diff --git a/src/device/service/drivers/xr/README_XR.md b/src/device/service/drivers/xr/README_XR.md
index c741c3e808ebddd20c9c4749064964594ea32b73..fa1bc944035d27769cd9c16e0c29318e554e9489 100644
--- a/src/device/service/drivers/xr/README_XR.md
+++ b/src/device/service/drivers/xr/README_XR.md
@@ -146,6 +146,18 @@ arbitrary endpoints in the topology (with consequent underlying XR service insta
     PYTHONPATH=../../../../ ./service-cli.py list
     PYTHONPATH=../../../../ ./service-cli.py delete 43a8046a-5dec-463d-82f7-7cc3442dbf4f
 ```
+
+It is also possible to create direct XR services without multi-layer services. E.g.:
+```
+    PYTHONPATH=../../../../  ./service-cli.py create-xr FooService X1-XR-CONSTELLATION  "XR HUB 1|XR-T1" "XR LEAF 2|XR-T1"
+```
+
+Additionally it is possible to list services and endpoints:
+```
+    PYTHONPATH=../../../../  ./service-cli.py list-endpoints
+    PYTHONPATH=../../../../  ./service-cli.py delete 43a8046a-5dec-463d-82f7-7cc3442dbf4f
+```
+
 The PYTHONPATH is mandatory. Suitable topology JSON must have been loaded before. With the
 CocroachDB persistence, it is sufficient to load the topology once and it will persist.
 
diff --git a/src/device/service/drivers/xr/service-cli.py b/src/device/service/drivers/xr/service-cli.py
index 01bd2aaa118225cf74a953fff81b54abb857e39b..7ab9606cef7bd7d3cca4f414cbd704ab150c8f52 100755
--- a/src/device/service/drivers/xr/service-cli.py
+++ b/src/device/service/drivers/xr/service-cli.py
@@ -19,21 +19,33 @@
 #
 # Run in this directory with PYTHONPATH=../../../../
 # E.g.:
+#   Create multi-layer service (L2 VPN over XR):
 #     PYTHONPATH=../../../../  ./service-cli.py create 1 R1-EMU 13/1/2 500 2 R3-EMU 13/1/2 500
+#   Single-layer (XR without services on top of it):
+#     PYTHONPATH=../../../../  ./service-cli.py create-xr FooService X1-XR-CONSTELLATION  "XR HUB 1|XR-T1" "XR LEAF 2|XR-T1"
+#   List services:
 #     PYTHONPATH=../../../../  ./service-cli.py list
+#   List possible endpoints:
+#     PYTHONPATH=../../../../  ./service-cli.py list-endpoints
+#   Delete service (if multi-layer, always deleter highest layer!)
 #     PYTHONPATH=../../../../  ./service-cli.py delete 43a8046a-5dec-463d-82f7-7cc3442dbf4f
 
-
 import argparse
 import logging
-import traceback
+from copy import deepcopy
+from dataclasses import dataclass, field
+from typing import Dict
 from contextlib import contextmanager
 
 from common.Settings import get_setting
 from context.client.ContextClient import ContextClient
+from service.client.ServiceClient import ServiceClient
 from tests.tools.mock_osm.MockOSM import MockOSM
-from common.proto.context_pb2 import ContextId, ServiceTypeEnum, ServiceStatusEnum
+from common.proto.context_pb2 import ContextId, ServiceTypeEnum, ServiceStatusEnum, Service, Empty, ServiceId
 from common.tools.grpc.Tools import grpc_message_to_json_string
+from common.tools.object_factory.Context import json_context, json_context_id
+from common.tools.object_factory.Topology import json_topology_id
+from common.tools.object_factory.ConfigRule import json_config_rule_set
 
 LOGGER = logging.getLogger(__name__)
 
@@ -48,11 +60,52 @@ def make_context_client():
     finally:
         _client.close()
 
+@contextmanager
+def make_service_client():
+    try:
+        _client = ServiceClient(get_setting('SERVICESERVICE_SERVICE_HOST'), get_setting('SERVICESERVICE_SERVICE_PORT_GRPC'))
+        yield _client
+    finally:
+        _client.close()
+
 def make_osm_wim():
     wim_url = 'http://{:s}:{:s}'.format(
         get_setting('COMPUTESERVICE_SERVICE_HOST'), str(get_setting('COMPUTESERVICE_SERVICE_PORT_HTTP')))
     return MockOSM(wim_url, WIM_MAPPING, WIM_USERNAME, WIM_PASSWORD)
 
+@dataclass
+class DevInfo:
+    name: str
+    uuid: str
+    endpoints: Dict[str, str] = field(default_factory= dict)
+    endpoints_by_uuid: Dict[str, str] = field(default_factory= dict)
+
+    def get_endpoint_uuid_or_exit(self, ep_name: str) -> str:
+        if ep_name not in self.endpoints:
+            print(f"Endpoint {ep_name} does not exist in device {self.name}. See \"service-cli.py list-endpoints\"")
+            exit(-1)
+        return self.endpoints[ep_name]
+
+def get_devices(cc: ContextClient) -> Dict[str, DevInfo]:
+    r = cc.ListDevices(Empty())
+    # print(grpc_message_to_json_string(r))
+
+    devices = dict()
+    for dev in r.devices:
+        di = DevInfo(dev.name, dev.device_id.device_uuid.uuid)
+        for ep in dev.device_endpoints:
+            di.endpoints[ep.name] = ep.endpoint_id.endpoint_uuid.uuid
+            di.endpoints_by_uuid[ep.endpoint_id.endpoint_uuid.uuid] = ep.name
+        devices[dev.name] = di
+    return devices
+
+def get_endpoint_map(devices: Dict[str, DevInfo]):
+    ep_map = dict()
+    for dev in devices.values():
+        for ep_name, ep_uuid in dev.endpoints.items():
+            ep_map[ep_uuid] = (dev.name, ep_name)
+    return ep_map
+
 logging.basicConfig(level=logging.ERROR)
 
 parser = argparse.ArgumentParser(description='TF Service Management Utility')
@@ -74,6 +127,13 @@ delete_parser = subparsers.add_parser('delete')
 delete_parser.add_argument('service_uuid', type=str, help='UUID of the service to be deleted')
 
 list_parser = subparsers.add_parser('list')
+list_parser = subparsers.add_parser('list-endpoints')
+
+create_xr_parser = subparsers.add_parser('create-xr')
+create_xr_parser.add_argument('service_name', type=str, help='Service Name')
+create_xr_parser.add_argument('constellation', type=str, help='XR Constellation')
+create_xr_parser.add_argument('interface1', type=str, help='One endpoint of the service')
+create_xr_parser.add_argument('interface2', type=str, help='Second endpoint of the service')
 
 args = parser.parse_args()
 
@@ -103,12 +163,17 @@ else:
     WIM_SERVICE_CONNECTION_POINTS = []
 
 #print(str(args))
-print(f"=== WIM_SERVICE_TYPE: {WIM_SERVICE_TYPE}")
-print(f"=== WIM_SERVICE_CONNECTION_POINTS: {WIM_SERVICE_CONNECTION_POINTS}")
-print(f"=== WIM_MAPPING: {WIM_MAPPING}")
+#print(f"=== WIM_SERVICE_TYPE: {WIM_SERVICE_TYPE}")
+#print(f"=== WIM_SERVICE_CONNECTION_POINTS: {WIM_SERVICE_CONNECTION_POINTS}")
+#print(f"=== WIM_MAPPING: {WIM_MAPPING}")
 
 with make_context_client() as client:
-    osm_wim = make_osm_wim();
+    # We only permit one context on our demos/testing
+    response = client.ListContextIds(Empty())
+    assert len(response.context_ids) == 1
+    context_uuid=json_context_id(response.context_ids[0].context_uuid.uuid)
+
+    osm_wim = make_osm_wim()
 
     if args.command == "create":
         service_uuid = osm_wim.create_connectivity_service(WIM_SERVICE_TYPE, WIM_SERVICE_CONNECTION_POINTS)
@@ -117,28 +182,122 @@ with make_context_client() as client:
         print(f"*** Get created service status --> {str(status)}")
 
     elif args.command == "delete":
-        osm_wim.wim.check_credentials()
+        service_id = {
+            "context_id": context_uuid,
+            "service_uuid": {
+                "uuid": args.service_uuid
+            }
+        }
+
         try:
-            osm_wim.wim.delete_connectivity_service(args.service_uuid)
-            print(f"*** Service {args.service_uuid} is no longer present (delete was successfull or service did not exist)")
-        except Exception as e:
-            print(f"*** Failed to delete service {args.service_uuid}, {e}")
+            response = client.GetService(ServiceId(**service_id))
+            #print(grpc_message_to_json_string(response))
+
+            high_level_delete = response.service_type == ServiceTypeEnum.SERVICETYPE_L2NM or response.service_type == ServiceTypeEnum.SERVICETYPE_L3NM
+            print(f"Deleting service {response.name}, type {ServiceTypeEnum.Name(response.service_type)}, {high_level_delete=}")
+
+        except:
+            print(f"No service with uuid {args.service_uuid} ({service_id})")
+            exit(-1)
+
+        if high_level_delete:
+            osm_wim.wim.check_credentials()
+            try:
+                osm_wim.wim.delete_connectivity_service(args.service_uuid)
+                print(f"*** Service {args.service_uuid} deleted (L2SM/L3SM layer)")
+            except Exception as e:
+                print(f"*** Failed to delete service {args.service_uuid}, {e}")
+        else:
+            with make_service_client() as service_client:
+                try:
+                    service_client.DeleteService(ServiceId(**service_id))
+                    print(f"*** Service {args.service_uuid} deleted (low level)")
+                except Exception as e:
+                    print(f"*** Failed to delete service {args.service_uuid}, {e}")
+
+    elif args.command == "create-xr":
+        CONTEXT_NAME = 'admin'
+        CONTEXT_ID   = json_context_id(CONTEXT_NAME)
+        CONTEXT      = json_context(CONTEXT_NAME, name=CONTEXT_NAME)
+
+        json_tapi_settings = {
+            'capacity_value'  : 50.0,
+            'capacity_unit'   : 'GHz',
+            'layer_proto_name': 'PHOTONIC_MEDIA',
+            'layer_proto_qual': 'tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC',
+            'direction'       : 'UNIDIRECTIONAL',
+        }
+        config_rule = json_config_rule_set('/settings', json_tapi_settings)
+
+        devices = get_devices(client)
+        if args.constellation not in devices:
+            print(f"Constellation {args.constellation} does not exist as a device. See \"service-cli.py list-endpoints\"")
+            exit(-1)
+        else:
+            dev_info = devices[args.constellation]
+            constellation_uuid = dev_info.uuid
+
+        interface1_uuid = dev_info.get_endpoint_uuid_or_exit(args.interface1)
+        interface2_uuid = dev_info.get_endpoint_uuid_or_exit(args.interface2)
+
+        print(f"Constellation {args.constellation:40}: {constellation_uuid:36}")
+        print(f"Interface 1   {args.interface1:40}: {interface1_uuid:36}")
+        print(f"Interface 2   {args.interface2:40}: {interface2_uuid:36}")
+
+        service_request = {
+            "name": args.service_name,
+            "service_id": {
+                 "context_id": {"context_uuid": {"uuid": response.context_ids[0].context_uuid.uuid}},
+                 "service_uuid": {"uuid": args.service_name}
+            },
+            'service_type'        : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE,
+            "service_endpoint_ids": [
+                {'device_id': {'device_uuid': {'uuid': constellation_uuid}}, 'endpoint_uuid': {'uuid': interface1_uuid}, 'topology_id': json_topology_id("admin", context_id=context_uuid)},
+                {'device_id': {'device_uuid': {'uuid': constellation_uuid}}, 'endpoint_uuid': {'uuid': interface2_uuid}, 'topology_id': json_topology_id("admin", context_id=context_uuid)}
+            ],
+            'service_status'      : {'service_status': ServiceStatusEnum.SERVICESTATUS_PLANNED},
+            'service_constraints' : [],
+        }
+
+        with make_service_client() as service_client:
+            sr = deepcopy(service_request)
+            endpoints, sr['service_endpoint_ids'] = sr['service_endpoint_ids'], []
+            create_response = service_client.CreateService(Service(**sr))
+            print(f'CreateService: {grpc_message_to_json_string(create_response)}')
+
+            sr['service_endpoint_ids'] = endpoints
+            #sr['service_id']['service_uuid'] = create_response
+            sr['service_config'] = {'config_rules': [config_rule]}
+
+            update_response = service_client.UpdateService(Service(**sr))
+            print(f'UpdateService: {grpc_message_to_json_string(update_response)}')
+
     elif args.command == "list":
+        devices = get_devices(client)
+        ep_map = get_endpoint_map(devices)
+
         response = client.ListServices(ContextId(**CONTEXT_ID))
 
-        #print('Services[{:d}] = {:s}'.format(len(response.services), grpc_message_to_json_string(response)))
+        # print('Services[{:d}] = {:s}'.format(len(response.services), grpc_message_to_json_string(response)))
         for service in response.services:
             scs = ""
-            
-            # See if there are endpoint constraints that might be regognizable by the user.
-            # Keys do not necessarily exist, so catch exceptions and ignore those constraints
-            # that we cannot easily represent.
-            for sc in service.service_constraints:
-                try:
-                    scs += f"{sc.endpoint_location.endpoint_id.device_id.device_uuid.uuid}:{sc.endpoint_location.endpoint_id.endpoint_uuid.uuid} "
-                except Exception:
-                    pass
 
-            print(f"{service.service_id.service_uuid.uuid:36}  {ServiceTypeEnum.Name(service.service_type):40}  {ServiceStatusEnum.Name(service.service_status.service_status)}  {scs}")
+            ep_list = []
+            for ep in service.service_endpoint_ids:
+                ep_uuid = ep.endpoint_uuid.uuid
+                if ep_uuid in ep_map:
+                    dev_name, ep_name = ep_map[ep_uuid]
+                    ep_list.append(f"{dev_name}:{ep_name}")
+            ep_list.sort()
+            eps = ", ".join(ep_list)
 
+            #print(f"{service.service_id.service_uuid.uuid:36}  {ServiceTypeEnum.Name(service.service_type):40}  {service.name:40}  {ServiceStatusEnum.Name(service.service_status.service_status)}  {scs}")
+            print(f"{service.service_id.service_uuid.uuid:36}  {ServiceTypeEnum.Name(service.service_type):40}  {service.name:40}  {ServiceStatusEnum.Name(service.service_status.service_status):28}  {eps}")
 
+    elif args.command == "list-endpoints":
+        devices = get_devices(client)
+        for name in sorted(devices.keys()):
+            dev = devices[name]
+            print(f"{name:40}    {dev.uuid:36}")
+            for ep_name in sorted(dev.endpoints.keys()):
+                print(f"    {ep_name:40}    {dev.endpoints[ep_name]:36}")
diff --git a/src/device/service/drivers/xr/setup_test_env.sh b/src/device/service/drivers/xr/setup_test_env.sh
index 92ff4a0312fb8f963f934f4cfd8d18603675aed0..bd5463cd4f9d08c903fc601cfcb7241b672e7681 100755
--- a/src/device/service/drivers/xr/setup_test_env.sh
+++ b/src/device/service/drivers/xr/setup_test_env.sh
@@ -17,7 +17,11 @@ export CONTEXTSERVICE_SERVICE_HOST=$(kubectl get service/contextservice --namesp
 export CONTEXTSERVICE_SERVICE_PORT_GRPC=$(kubectl get service/contextservice --namespace tfs  -o jsonpath='{.spec.ports[?(@.name=="grpc")].port}')
 export COMPUTESERVICE_SERVICE_HOST=$(kubectl get service/computeservice --namespace tfs  --template '{{.spec.clusterIP}}')
 export COMPUTESERVICE_SERVICE_PORT_HTTP=$(kubectl get service/computeservice --namespace tfs  -o jsonpath='{.spec.ports[?(@.name=="http")].port}')
+export SERVICESERVICE_SERVICE_HOST=$(kubectl get service/serviceservice --namespace tfs  --template '{{.spec.clusterIP}}')
+export SERVICESERVICE_SERVICE_PORT_GRPC=$(kubectl get service/serviceservice --namespace tfs  -o jsonpath='{.spec.ports[?(@.name=="grpc")].port}')
 echo "CONTEXTSERVICE_SERVICE_HOST=$CONTEXTSERVICE_SERVICE_HOST"
 echo "CONTEXTSERVICE_SERVICE_PORT_GRPC=$CONTEXTSERVICE_SERVICE_PORT_GRPC"
 echo "COMPUTESERVICE_SERVICE_HOST=$COMPUTESERVICE_SERVICE_HOST"
 echo "COMPUTESERVICE_SERVICE_PORT_HTTP=$COMPUTESERVICE_SERVICE_PORT_HTTP"
+echo "SERVICESERVICE_SERVICE_HOST=$SERVICESERVICE_SERVICE_HOST"
+echo "SERVICESERVICE_SERVICE_PORT_GRPC=$SERVICESERVICE_SERVICE_PORT_GRPC"