diff --git a/src/monitoring/client/monitoring_client.py b/src/monitoring/client/monitoring_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..fe7ac285d59692f2e14034abaa491a12c2c1b845
--- /dev/null
+++ b/src/monitoring/client/monitoring_client.py
@@ -0,0 +1,150 @@
+# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import sys
+import grpc
+
+from monitoring.proto import monitoring_pb2
+from monitoring.proto import monitoring_pb2_grpc
+from monitoring.proto import context_pb2
+
+from common.logger import getJSONLogger
+LOGGER = getJSONLogger('monitoring-client')
+LOGGER.setLevel('DEBUG')
+
+class MonitoringClient:
+
+    def __init__(self, server='monitoring', port='7070'):
+        endpoint = '{}:{}'.format(server, port)
+        LOGGER.info('init monitoringClient {}'.format(endpoint))
+        self.channel = grpc.insecure_channel(endpoint)
+        self.server = monitoring_pb2_grpc.MonitoringServiceStub(self.channel)
+
+    def CreateKpi(self, request):
+        LOGGER.info('CreateKpi: {}'.format(request))
+        response = self.server.CreateKpi(request)
+        LOGGER.info('CreateKpi result: {}'.format(response))
+        return response
+
+    def EditKpiDescriptor(self, request):
+        LOGGER.info('EditKpiDescriptor: {}'.format(request))
+        response = self.server.EditKpiDescriptor(request)
+        LOGGER.info('EditKpiDescriptor result: {}'.format(response))
+        return response
+
+    def DeleteKpi(self, request):
+        LOGGER.info('DeleteKpi: {}'.format(request))
+        response = self.server.DeleteKpi(request)
+        LOGGER.info('DeleteKpi result: {}'.format(response))
+        return response
+
+    def GetKpiDescriptorList(self, request):
+        LOGGER.info('GetKpiDescriptorList: {}'.format(request))
+        response = self.server.GetKpiDescriptorList(request)
+        LOGGER.info('GetKpiDescriptorList result: {}'.format(response))
+        return response
+
+    def CreateBundleKpi(self, request):
+        LOGGER.info('CreateBundleKpi: {}'.format(request))
+        response = self.server.CreateBundleKpi(request)
+        LOGGER.info('CreateBundleKpi result: {}'.format(response))
+        return response
+
+    def GetKpiDescriptor(self, request):
+        LOGGER.info('GetKpiDescriptor: {}'.format(request))
+        response = self.server.GetKpiDescriptor(request)
+        LOGGER.info('GetKpiDescriptor result: {}'.format(response))
+        return response
+
+    def MonitorKpi(self, request):
+        LOGGER.info('MonitorKpi: {}'.format(request))
+        response = self.server.MonitorKpi(request)
+        LOGGER.info('MonitorKpi result: {}'.format(response))
+        return response
+
+    def IncludeKpi(self, request):
+        LOGGER.info('IncludeKpi: {}'.format(request))
+        response = self.server.IncludeKpi(request)
+        LOGGER.info('IncludeKpi result: {}'.format(response))
+        return response
+
+    def QueryKpiData(self, request):
+        LOGGER.info('QueryKpiData: {}'.format(request))
+        response = self.server.QueryKpiData(request)
+        LOGGER.info('QueryKpiData result: {}'.format(response))
+        return response
+
+    def SubscribeKpi(self, request):
+        LOGGER.info('SubscribeKpi: {}'.format(request))
+        response = self.server.SubscribeKpi(request)
+        LOGGER.info('SubscribeKpi result: {}'.format(response))
+        return response
+
+    def GetSubsDescriptor(self, request):
+        LOGGER.info('GetSubsDescriptor: {}'.format(request))
+        response = self.server.GetSubsDescriptor(request)
+        LOGGER.info('GetSubsDescriptor result: {}'.format(response))
+        return response
+
+    def EditKpiSubscription(self, request):
+        LOGGER.info('EditKpiSubscription: {}'.format(request))
+        response = self.server.EditKpiSubscription(request)
+        LOGGER.info('EditKpiSubscription result: {}'.format(response))
+        return response
+
+    def CreateKpiAlarm(self, request):
+        LOGGER.info('CreateKpiAlarm: {}'.format(request))
+        response = self.server.CreateKpiAlarm(request)
+        LOGGER.info('CreateKpiAlarm result: {}'.format(response))
+        return response
+
+    def EditKpiAlarm(self, request):
+        LOGGER.info('EditKpiAlarm: {}'.format(request))
+        response = self.server.EditKpiAlarm(request)
+        LOGGER.info('EditKpiAlarm result: {}'.format(response))
+        return response
+
+    def GetAlarms(self, request):
+        LOGGER.info('GetAlarms: {}'.format(request))
+        response = self.server.GetAlarms(request)
+        LOGGER.info('GetAlarms result: {}'.format(response))
+        return response
+
+    def GetAlarmDescriptor(self, request):
+        LOGGER.info('GetAlarmDescriptor: {}'.format(request))
+        response = self.server.GetAlarmDescriptor(request)
+        LOGGER.info('GetAlarmDescriptor result: {}'.format(response))
+        return response
+
+
+    # def GetStreamKpi(self, request):
+    #     LOGGER.info('GetStreamKpi: {}'.format(request))
+    #     response = self.server.GetStreamKpi(request)
+    #     LOGGER.info('GetStreamKpi result: {}'.format(response))
+    #     yield monitoring_pb2.Kpi()
+    #
+    # def GetInstantKpi(self, request):
+    #     LOGGER.info('GetInstantKpi: {}'.format(request))
+    #     response = self.server.GetInstantKpi(request)
+    #     LOGGER.info('GetInstantKpi result: {}'.format(response))
+    #     return monitoring_pb2.Kpi()
+
+
+
+if __name__ == '__main__':
+    # get port
+    port = sys.argv[1] if len(sys.argv) > 1 else '7070'
+
+    # make call to server
+    client = MonitoringClient(port=port)
diff --git a/src/monitoring/genproto_win.sh b/src/monitoring/genproto_win.sh
new file mode 100644
index 0000000000000000000000000000000000000000..723e7f74c0751d72d9fd888fc67dea6cd5fd8b69
--- /dev/null
+++ b/src/monitoring/genproto_win.sh
@@ -0,0 +1,13 @@
+#!/bin/bash -eu
+
+py -m grpc_tools.protoc -I../../proto --python_out=proto --grpc_python_out=proto monitoring.proto
+py -m grpc_tools.protoc -I../../proto --python_out=proto --grpc_python_out=proto context.proto
+py -m grpc_tools.protoc -I../../proto --python_out=proto --grpc_python_out=proto kpi_sample_types.proto
+
+rm proto/context_pb2_grpc.py
+rm proto/kpi_sample_types_pb2_grpc.py
+
+sed -i -E 's/(import\ .*)_pb2/from . \1_pb2/g' proto/monitoring_pb2.py
+sed -i -E 's/(import\ .*)_pb2/from . \1_pb2/g' proto/monitoring_pb2_grpc.py
+sed -i -E 's/(import\ .*)_pb2/from . \1_pb2/g' proto/context_pb2.py
+
diff --git a/src/monitoring/service/MonitoringServiceServicerImpl.py b/src/monitoring/service/MonitoringServiceServicerImpl.py
index 28c1ed12045a8d6dfbd9669e8ce2f081248cabd4..e3d986fc3169f400b9e2d2d0b283e1b20071625c 100644
--- a/src/monitoring/service/MonitoringServiceServicerImpl.py
+++ b/src/monitoring/service/MonitoringServiceServicerImpl.py
@@ -77,6 +77,55 @@ class MonitoringServiceServicerImpl(monitoring_pb2_grpc.MonitoringServiceService
             # CREATEKPI_COUNTER_FAILED.inc()
             grpc_context.abort(grpc.StatusCode.INTERNAL, str(e))
 
+    def EditKpiDescriptor ( self, request : monitoring_pb2.EditedKpiDescriptor, grpc_context : grpc.ServicerContext) -> context_pb2.Empty:
+
+        LOGGER.info('EditKpiDescriptor')
+        try:
+             # TBC
+            return context_pb2.Empty()
+        except ServiceException as e:
+            LOGGER.exception('EditKpiDescriptor exception')
+            grpc_context.abort(e.code, e.details)
+        except Exception as e:  # pragma: no cover
+            LOGGER.exception('EditKpiDescriptor exception')
+
+    def DeleteKpi ( self, request : monitoring_pb2.KpiId, grpc_context : grpc.ServicerContext) -> context_pb2.Empty:
+
+        LOGGER.info('DeleteKpi')
+        try:
+             # TBC
+            return context_pb2.Empty()
+        except ServiceException as e:
+            LOGGER.exception('DeleteKpi exception')
+            grpc_context.abort(e.code, e.details)
+        except Exception as e:  # pragma: no cover
+            LOGGER.exception('DeleteKpi exception')
+
+    def GetKpiDescriptorList ( self, request : context_pb2.Empty, grpc_context : grpc.ServicerContext) -> monitoring_pb2.KpiDescriptorList:
+
+        LOGGER.info('GetKpiDescriptorList')
+        try:
+             # TBC
+            return monitoring_pb2.KpiDescriptorList()
+        except ServiceException as e:
+            LOGGER.exception('GetKpiDescriptorList exception')
+            grpc_context.abort(e.code, e.details)
+        except Exception as e:  # pragma: no cover
+            LOGGER.exception('GetKpiDescriptorList exception')
+
+    def CreateBundleKpi ( self, request : monitoring_pb2.BundleKpiDescriptor, grpc_context : grpc.ServicerContext) -> monitoring_pb2.KpiId:
+
+        LOGGER.info('CreateBundleKpi')
+        try:
+             # TBC
+            return monitoring_pb2.KpiId()
+        except ServiceException as e:
+            LOGGER.exception('CreateBundleKpi exception')
+            grpc_context.abort(e.code, e.details)
+        except Exception as e:  # pragma: no cover
+            LOGGER.exception('CreateBundleKpi exception')
+
+
     # rpc MonitorKpi (MonitorKpiRequest) returns (context.Empty) {}
     def MonitorKpi(
         self, request : monitoring_pb2.MonitorKpiRequest, grpc_context : grpc.ServicerContext
@@ -141,16 +190,16 @@ class MonitoringServiceServicerImpl(monitoring_pb2_grpc.MonitoringServiceService
             # CREATEKPI_COUNTER_FAILED.inc()
         return context_pb2.Empty()
 
-    def GetStreamKpi ( self, request, grpc_context : grpc.ServicerContext):
-        # receives monitoring.KpiId returns stream monitoring.Kpi
-        LOGGER.info('GetStreamKpi')
-        yield monitoring_pb2.Kpi()
-
-    @MONITORING_GETINSTANTKPI_REQUEST_TIME.time()
-    def GetInstantKpi ( self, request, grpc_context : grpc.ServicerContext):
-        # receives monitoring.KpiId returns monitoring.Kpi
-        LOGGER.info('GetInstantKpi')
-        return monitoring_pb2.Kpi()
+    # def GetStreamKpi ( self, request, grpc_context : grpc.ServicerContext):
+    #
+    #     LOGGER.info('GetStreamKpi')
+    #     yield monitoring_pb2.Kpi()
+    #
+    # @MONITORING_GETINSTANTKPI_REQUEST_TIME.time()
+    # def GetInstantKpi ( self, request, grpc_context : grpc.ServicerContext):
+    #
+    #     LOGGER.info('GetInstantKpi')
+    #     return monitoring_pb2.Kpi()
 
 
     def GetKpiDescriptor(
@@ -178,3 +227,112 @@ class MonitoringServiceServicerImpl(monitoring_pb2_grpc.MonitoringServiceService
 
         except Exception:  # pragma: no cover
             LOGGER.exception('GetKpiDescriptor exception')
+
+    def QueryKpiData ( self, request : monitoring_pb2.KpiQuery, grpc_context : grpc.ServicerContext) -> monitoring_pb2.KpiList:
+
+        LOGGER.info('QueryKpiData')
+        try:
+             # TBC
+            return monitoring_pb2.KpiQuery()
+        except ServiceException as e:
+            LOGGER.exception('QueryKpiData exception')
+            grpc_context.abort(e.code, e.details)
+        except Exception as e:  # pragma: no cover
+            LOGGER.exception('QueryKpiData exception')
+
+    def SubscribeKpi ( self, request : monitoring_pb2.SubsDescriptor, grpc_context : grpc.ServicerContext) -> monitoring_pb2.KpiList:
+
+        LOGGER.info('SubscribeKpi')
+        try:
+             # TBC
+            yield monitoring_pb2.KpiList()
+        except ServiceException as e:
+            LOGGER.exception('SubscribeKpi exception')
+            grpc_context.abort(e.code, e.details)
+        except Exception as e:  # pragma: no cover
+            LOGGER.exception('SubscribeKpi exception')
+
+
+    def GetSubsDescriptor ( self, request : monitoring_pb2.SubscriptionID, grpc_context : grpc.ServicerContext) -> monitoring_pb2.SubsDescriptor:
+
+        LOGGER.info('GetSubsDescriptor')
+        try:
+             # TBC
+            return monitoring_pb2.SubsDescriptor()
+        except ServiceException as e:
+            LOGGER.exception('GetSubsDescriptor exception')
+            grpc_context.abort(e.code, e.details)
+        except Exception as e:  # pragma: no cover
+            LOGGER.exception('GetSubsDescriptor exception')
+
+    def GetSubscriptions ( self, request : context_pb2.Empty, grpc_context : grpc.ServicerContext) -> monitoring_pb2.SubsIDList:
+
+        LOGGER.info('GetSubscriptions')
+        try:
+             # TBC
+            return monitoring_pb2.SubsIDList()
+        except ServiceException as e:
+            LOGGER.exception('GetSubscriptions exception')
+            grpc_context.abort(e.code, e.details)
+        except Exception as e:  # pragma: no cover
+            LOGGER.exception('GetSubscriptions exception')
+
+    def EditKpiSubscription ( self, request : monitoring_pb2.SubsDescriptor, grpc_context : grpc.ServicerContext) -> context_pb2.Empty:
+
+        LOGGER.info('EditKpiSubscription')
+        try:
+             # TBC
+            return context_pb2.Empty()
+        except ServiceException as e:
+            LOGGER.exception('EditKpiSubscription exception')
+            grpc_context.abort(e.code, e.details)
+        except Exception as e:  # pragma: no cover
+            LOGGER.exception('EditKpiSubscription exception')
+
+    def CreateKpiAlarm ( self, request : monitoring_pb2.AlarmDescriptor, grpc_context : grpc.ServicerContext) -> monitoring_pb2.AlarmResponse:
+
+        LOGGER.info('CreateKpiAlarm')
+        try:
+             # TBC
+            return monitoring_pb2.AlarmResponse()
+        except ServiceException as e:
+            LOGGER.exception('CreateKpiAlarm exception')
+            grpc_context.abort(e.code, e.details)
+        except Exception as e:  # pragma: no cover
+            LOGGER.exception('CreateKpiAlarm exception')
+
+    def EditKpiAlarm ( self, request : monitoring_pb2.AlarmDescriptor, grpc_context : grpc.ServicerContext) -> context_pb2.Empty:
+
+        LOGGER.info('EditKpiAlarm')
+        try:
+             # TBC
+            return context_pb2.Empty()
+        except ServiceException as e:
+            LOGGER.exception('EditKpiAlarm exception')
+            grpc_context.abort(e.code, e.details)
+        except Exception as e:  # pragma: no cover
+            LOGGER.exception('EditKpiAlarm exception')
+
+    def GetAlarms ( self, request : context_pb2.Empty, grpc_context : grpc.ServicerContext) -> monitoring_pb2.AlarmIDList:
+
+        LOGGER.info('GetAlarms')
+        try:
+             # TBC
+            return monitoring_pb2.AlarmIDList()
+        except ServiceException as e:
+            LOGGER.exception('GetAlarms exception')
+            grpc_context.abort(e.code, e.details)
+        except Exception as e:  # pragma: no cover
+            LOGGER.exception('GetAlarms exception')
+
+    def GetAlarmDescriptor ( self, request : monitoring_pb2.AlarmID, grpc_context : grpc.ServicerContext) -> monitoring_pb2.AlarmDescriptor:
+
+        LOGGER.info('GetAlarmDescriptor')
+        try:
+             # TBC
+            return monitoring_pb2.AlarmDescriptor()
+        except ServiceException as e:
+            LOGGER.exception('GetAlarmDescriptor exception')
+            grpc_context.abort(e.code, e.details)
+        except Exception as e:  # pragma: no cover
+            LOGGER.exception('GetAlarmDescriptor exception')
\ No newline at end of file