diff --git a/README.md b/README.md
index 424b923622e7a903c0b4e2704d606e956ce69b5a..66e44bc650184f1fc7d230c59d93c4e49dc5d856 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[PACKAGE]: ../../raw/master/mkp/bgp_peer-2.2.8-20250117.mkp "bgp_peer-2.2.8-20250117.mkp"
+[PACKAGE]: ../../raw/master/mkp/bgp_peer-2.3.0-20250329.mkp "bgp_peer-2.3.0-20250329.mkp"
 # BGP Peer
 
 Check plugin to monitor the status of BGP peers and inventory plugin for static BGP peer data.
diff --git a/mkp/bgp_peer-2.3.0-20250329.mkp b/mkp/bgp_peer-2.3.0-20250329.mkp
new file mode 100644
index 0000000000000000000000000000000000000000..e3f51db18d304f8228fdfbd54a2c2c9c0632aed8
Binary files /dev/null and b/mkp/bgp_peer-2.3.0-20250329.mkp differ
diff --git a/source/agent_based/bgp_peer.py b/source/agent_based/bgp_peer.py
index df8172bb410fdfcae76032a51ff6e20a28d07f4a..32f65c748919a875f8cf287f2a544783f79da38d 100644
--- a/source/agent_based/bgp_peer.py
+++ b/source/agent_based/bgp_peer.py
@@ -1,328 +1,2 @@
 #!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# License: GNU General Public License v2
-
-# Author: thl-cmk[at]outlook[dot]com
-# URL   : https://thl-cmk.hopto.org
-# Date  : 2021-08-20
-
-# based on the BGP peer plugin by Thomas Wollner (tw@wollner-net.de)
-#
-# 2021-08-20: rewritten for CMK 2.0 by thl-cmk[at]outlook[dot]com
-# 2021-08-21: added more perfdata, added metrics file
-# 2021-08-22: added WATO options, streamlined with cisco_bgp_peer
-# 2021-08-29: moved static (longoutput) to inventory plugin
-#             moved helper functions to utils/bgp_peer
-# 2021-11-14: merged check function with cisco_bgp_peer
-#             moved parse function to utils/bgp_peer
-# 2021-04-02: rewritten bgp neighbor state handling (made configurable)
-# 2022-04-29: added upper/lower prefix limits from wato
-#             added info if device is admin prefix limit capable (device_admin_limit)
-# 2022-05-09: made item name configurable (don't use address-family/routing-instance/VRF)
-# 2022-05-11: changed bgp_get_peer_entry to get proper parameters instead of Nontransparent list
-#             added remote_as to BgpPeerItem
-# 2022-09-05: added missing wato parameters to register.check_plugin check_default_parameters
-# 2022-09-10: made more reliable on limited data (fsm_established_time and admin_state)
-#             (THX to martin[dot]pechstein[at]posteo[dot]de)
-# 2022-09-11: optimized internal flow: > alias > not found > admin down > peer state > ...
-# 2023-01-21: changed to always yield fsm_established_time (not only if beep connects, but also on all other states)
-# 2023-01-22: fix output for admin_state
-# 2023-02-16: changed for CMK 2.1 (moved gui files from local/share/.. to local/lib/..)
-#             fix type error in discovery (CMK2.1 GUI only)
-# 2023-02-20: fix crash if metric data is None
-# 2023-03-26: optimized output of metrics, GetRateError will not be set to 0 anymore
-# 2023-08-17: fix removed internal_item form check_default_parameters (THX mail[at]bastian-kuhn[dot]de)
-#             added warning if internal_item is missing in params
-# 2024-06-07: fixed crash on configured state mapping
-# 2024-12-20: added local address/as/identifier for bgp_topology
-
-# Example Agent Output:
-# BGP4-MIB
-
-# .1.3.6.1.2.1.15.3.1.1.192.168.254.2 = IpAddress: 192.168.254.2
-# .1.3.6.1.2.1.15.3.1.2.192.168.254.2 = INTEGER: 3
-# .1.3.6.1.2.1.15.3.1.3.192.168.254.2 = INTEGER: 2
-# .1.3.6.1.2.1.15.3.1.4.192.168.254.2 = INTEGER: 4
-# .1.3.6.1.2.1.15.3.1.5.192.168.254.2 = IpAddress: 0.0.0.0
-# .1.3.6.1.2.1.15.3.1.7.192.168.254.2 = IpAddress: 192.168.254.2
-# .1.3.6.1.2.1.15.3.1.9.192.168.254.2 = INTEGER: 65301
-# .1.3.6.1.2.1.15.3.1.10.192.168.254.2 = Counter32: 0
-# .1.3.6.1.2.1.15.3.1.11.192.168.254.2 = Counter32: 5
-# .1.3.6.1.2.1.15.3.1.14.192.168.254.2 = Hex-STRING: 04 00
-# .1.3.6.1.2.1.15.3.1.16.192.168.254.2 = Gauge32: 6586822
-#
-
-import time
-from typing import Dict, List
-
-from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
-    Result,
-    check_levels,
-    State,
-    SNMPTree,
-    exists,
-    get_rate,
-    GetRateError,
-    get_value_store,
-    # IgnoreResultsError,
-    Metric,
-    render,
-)
-from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
-    CheckResult,
-    StringTable,
-)
-from cmk.base.plugins.agent_based.utils.bgp_peer import (
-    BgpPeer,
-    bgp_get_peer_entry,
-    bgp_adminstate,
-    bgp_peerstate,
-)
-
-
-def parse_bgp_peer(string_table: List[StringTable]) -> Dict[str, BgpPeer] | None:
-    peer_table = {}
-    try:
-        peer_info, local_info = string_table
-    except ValueError:
-        return
-
-    try:
-        local_as, local_id = local_info[0]
-    except (IndexError, ValueError):
-        local_as, local_id = None, None
-
-    for entry in peer_info:
-        remote_addr, remote_id, peer_state, admin_state, local_addr, remote_as, in_updates, out_updates, \
-        in_messages, out_messages, fsm_established_transitions, fsm_established_time, in_update_elapsed_time = entry
-
-        bgp_peer = bgp_get_peer_entry(
-            admin_state=admin_state,
-            fsm_established_time=fsm_established_time,
-            fsm_established_transitions=fsm_established_transitions,
-            in_messages=in_messages,
-            in_update_elapsed_time=in_update_elapsed_time,
-            in_updates=in_updates,
-            local_addr=local_addr,
-            out_messages=out_messages,
-            out_updates=out_updates,
-            peer_state=peer_state,
-            remote_addr=remote_addr,
-            remote_as=remote_as,
-            remote_id=remote_id,
-            local_as=local_as,
-            local_id=local_id,
-        )
-        if bgp_peer:
-            peer_table.update(bgp_peer)
-    return peer_table
-
-
-def discovery_bgp_peer(params, section: Dict[str, BgpPeer]) -> DiscoveryResult:
-    _item_parts = [
-        'remote_address',
-        'remote_as',
-        'address_family',
-        'routing_instance',
-    ]
-    for key in section.keys():
-        parameters = {'internal_item': key}
-        item = ''
-        for item_part in _item_parts:
-            if item_part not in params['build_item']:
-                item += f'{section[key].item[item_part]} '
-        item = item.strip(' ')
-        yield Service(item=item, parameters=parameters)
-
-
-def bgp_peer_base_info(peer: BgpPeer) -> GeneratorExit:
-    for message, value in [
-        ('Local AS:', peer.local_as),
-        ('Local address:', peer.local_addr),
-        ('Local identifier:', peer.local_id),
-        ('Remote AS:', peer.remote_as),
-        ('Remote address:', peer.remote_addr),
-        ('Remote identifier:', peer.remote_id),
-    ]:
-        if value is not None:
-            yield Result(state=State.OK, notice=f'{message} {value}')
-
-
-def check_bgp_peer(item, params, section: Dict[str, BgpPeer]) -> CheckResult:
-    if not params.get('internal_item'):
-        yield Result(
-            state=State.WARN,
-            summary='This service is using old parameters (see details)',
-            details='This service is using old parameters. To refresh the parameters you need to do a "Tabula rasa" '
-                    '(Discover services -> Remove all and find new)',
-        )
-    else:
-        item = params['internal_item']
-
-    neighborstate = {
-        '1': 2,  # idle
-        '2': 1,  # connect
-        '3': 1,  # active
-        '4': 1,  # opensent
-        '5': 1,  # openconfirm
-        '6': 0,  # established
-    }
-
-    neighborstate.update(params['neighborstate'])
-    peer_not_found_state = params['peernotfound']
-
-    for bgp_connection, bgp_alias, not_found_state in params.get('peer_list', []):
-        if item == bgp_connection:
-            if bgp_alias != '':
-                yield Result(state=State.OK, summary=f'[{bgp_alias}]', details=' ')
-            peer_not_found_state = not_found_state
-
-    # get item data
-    try:
-        peer = section[item]
-    except KeyError:
-        yield Result(state=State(peer_not_found_state), summary='Item not found in SNMP data')
-        return
-
-    if peer.description:
-        yield Result(state=State.OK, summary=f'[{peer.description}]', details=' ')
-
-    if peer.admin_state == 1:  # shutdown
-        yield Result(state=State(params['admindown']), summary=f'Admin state: {bgp_adminstate(peer.admin_state)}')
-        yield from bgp_peer_base_info(peer)
-        return
-
-    yield Result(
-        state=State(neighborstate.get(str(peer.peer_state))),
-        notice=f'Peer state: {bgp_peerstate(peer.peer_state)}'
-    )
-
-    if peer.fsm_established_time:  # always yield fsm_established time
-        yield Metric(
-            name=f'bgp_peer_fsmestablishedtime',
-            value=peer.fsm_established_time,
-            boundaries=(0, None)
-        )
-
-    if not peer.peer_state == 6:  # not established
-        yield from bgp_peer_base_info(peer)
-        return
-
-    if peer.fsm_established_time:  # if peer is established use fsm_established_time as uptime
-        yield from check_levels(
-            value=peer.fsm_established_time,
-            label='Uptime',
-            levels_lower=params['minuptime'],
-            render_func=render.timespan,
-            metric_name='bgp_peer_fsmestablishedtime',
-        )
-
-    if peer.peer_unavail_reason != 0:  # huawei peer unavailable state
-        yield Result(state=State.CRIT, notice=F'Peer unavailable reason: {peer.peer_unavail_reason_str}')
-
-    if peer.device_admin_limit and peer.prefix_admin_limit is None:
-        yield Result(
-            state=State(params['noprefixlimit']),
-            notice='Prefix limit/warn threshold not configured on the device.',
-        )
-
-    acceptedprefixes = peer.accepted_prefixes
-    prefixadminlimit = peer.prefix_admin_limit
-    prefixthreshold = peer.prefix_threshold
-    warnthreshold = None
-
-    if prefixadminlimit is not None and prefixthreshold is not None:
-        warnthreshold = int(prefixadminlimit / 100.0 * prefixthreshold)  # use float (100.0) to get xx.xx in division
-    if acceptedprefixes is not None and peer.peer_state == 6:  # peer established and prefixes accepted
-        yield from check_levels(
-            value=acceptedprefixes,
-            metric_name='bgp_peer_acceptedprefixes',
-            levels_upper=params.get('accepted_prefixes_upper_levels', (warnthreshold, prefixadminlimit)),
-            levels_lower=params.get('accepted_prefixes_lower_levels'),
-            label='Prefixes accepted',
-            render_func=lambda v: f'{v}'
-        )
-
-    now_time = time.time()
-    value_store = get_value_store()
-
-    for key, value in peer.metric_rate:
-        try:
-            value = get_rate(value_store, f'{key}', now_time, value, raise_overflow=True)
-        except GetRateError:
-            continue
-        yield Metric(name=f'bgp_peer_{key}', value=value, boundaries=(0, None))
-
-    for key, value in peer.metric_count:
-        if value is not None:
-            yield Metric(name=f'bgp_peer_{key}', value=value, boundaries=(0, None))
-
-    yield from bgp_peer_base_info(peer)
-
-
-register.snmp_section(
-    name='bgp_peer',
-    parse_function=parse_bgp_peer,
-    fetch=[
-        SNMPTree(
-            base='.1.3.6.1.2.1.15.3.1',  # BGP4-MIB::BgpPeerEntry
-            oids=[
-                '7',  # bgpPeerRemoteAddr
-                '1',  # bgpPeerIdentifier
-                '2',  # bgpPeerState
-                '3',  # bgpPeerAdminStatus
-                '5',  # bgpPeerLocalAddr
-                '9',  # bgpPeerRemoteAs
-                '10',  # bgpPeerInUpdates
-                '11',  # bgpPeerOutUpdates
-                '12',  # bgpPeerInTotalMessages
-                '13',  # bgpPeerOutTotalMessages
-                '15',  # bgpPeerFsmEstablishedTransitions
-                '16',  # bgpPeerFsmEstablishedTime
-                '24',  # bgpPeerInUpdateElapsedTime
-            ]),
-        SNMPTree(
-            base='.1.3.6.1.2.1.15',  # BGP4-MIB
-            oids=[
-                '2',  # bgpLocalAs
-                '4',  # bgpIdentifier (local)
-            ])
-    ],
-    detect=exists('.1.3.6.1.2.1.15.3.1.1.*')
-)
-
-register.check_plugin(
-    name='bgp_peer',
-    service_name='BGP peer %s',
-    discovery_function=discovery_bgp_peer,
-    discovery_default_parameters={
-        'build_item': [
-            # 'remote_address',
-            'remote_as',
-            # 'address_family',
-            # 'routing_instance',
-        ]
-    },
-    discovery_ruleset_name='discovery_bgp_peer',
-    check_function=check_bgp_peer,
-    check_default_parameters={
-        'minuptime': (7200, 3600),
-        'peer_list': [],
-        'peernotfound': 2,
-        'admindown': 1,
-        'noprefixlimit': 1,
-        'neighborstate': {
-            '1': 2,  # idle
-            '2': 1,  # connect
-            '3': 1,  # active
-            '4': 1,  # opensent
-            '5': 1,  # openconfirm
-            '6': 0,  # established
-        },
-    },
-    check_ruleset_name='bgp_peer',
-)
+# dummy needed for CMK 2.30x to shadow the original file
\ No newline at end of file
diff --git a/source/cmk_addons_plugins/bgp_peer/agent_based/bgp_peer.py b/source/cmk_addons_plugins/bgp_peer/agent_based/bgp_peer.py
new file mode 100644
index 0000000000000000000000000000000000000000..e2f29fec27e9599df2c56015f8315c450a640aed
--- /dev/null
+++ b/source/cmk_addons_plugins/bgp_peer/agent_based/bgp_peer.py
@@ -0,0 +1,352 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# License: GNU General Public License v2
+
+# Author: thl-cmk[at]outlook[dot]com
+# URL   : https://thl-cmk.hopto.org
+# Date  : 2021-08-20
+
+# based on the BGP peer plugin by Thomas Wollner (tw@wollner-net.de)
+#
+# 2021-08-20: rewritten for CMK 2.0 by thl-cmk[at]outlook[dot]com
+# 2021-08-21: added more perfdata, added metrics file
+# 2021-08-22: added WATO options, streamlined with cisco_bgp_peer
+# 2021-08-29: moved static (longoutput) to inventory plugin
+#             moved helper functions to utils/bgp_peer
+# 2021-11-14: merged check function with cisco_bgp_peer
+#             moved parse function to utils/bgp_peer
+# 2021-04-02: rewritten bgp neighbor state handling (made configurable)
+# 2022-04-29: added upper/lower prefix limits from wato
+#             added info if device is admin prefix limit capable (device_admin_limit)
+# 2022-05-09: made item name configurable (don't use address-family/routing-instance/VRF)
+# 2022-05-11: changed bgp_get_peer_entry to get proper parameters instead of Nontransparent list
+#             added remote_as to BgpPeerItem
+# 2022-09-05: added missing wato parameters to register.check_plugin check_default_parameters
+# 2022-09-10: made more reliable on limited data (fsm_established_time and admin_state)
+#             (THX to martin[dot]pechstein[at]posteo[dot]de)
+# 2022-09-11: optimized internal flow: > alias > not found > admin down > peer state > ...
+# 2023-01-21: changed to always yield fsm_established_time (not only if beep connects, but also on all other states)
+# 2023-01-22: fix output for admin_state
+# 2023-02-16: changed for CMK 2.1 (moved gui files from local/share/.. to local/lib/..)
+#             fix type error in discovery (CMK2.1 GUI only)
+# 2023-02-20: fix crash if metric data is None
+# 2023-03-26: optimized output of metrics, GetRateError will not be set to 0 anymore
+# 2023-08-17: fix removed internal_item form check_default_parameters (THX mail[at]bastian-kuhn[dot]de)
+#             added warning if internal_item is missing in params
+# 2024-06-07: fixed crash on configured state mapping
+# 2024-12-20: added local address/as/identifier for bgp_topology
+# 2025-03-29: refactored for ruleset APIv1
+
+# Example Agent Output:
+# BGP4-MIB
+
+# .1.3.6.1.2.1.15.3.1.1.192.168.254.2 = IpAddress: 192.168.254.2
+# .1.3.6.1.2.1.15.3.1.2.192.168.254.2 = INTEGER: 3
+# .1.3.6.1.2.1.15.3.1.3.192.168.254.2 = INTEGER: 2
+# .1.3.6.1.2.1.15.3.1.4.192.168.254.2 = INTEGER: 4
+# .1.3.6.1.2.1.15.3.1.5.192.168.254.2 = IpAddress: 0.0.0.0
+# .1.3.6.1.2.1.15.3.1.7.192.168.254.2 = IpAddress: 192.168.254.2
+# .1.3.6.1.2.1.15.3.1.9.192.168.254.2 = INTEGER: 65301
+# .1.3.6.1.2.1.15.3.1.10.192.168.254.2 = Counter32: 0
+# .1.3.6.1.2.1.15.3.1.11.192.168.254.2 = Counter32: 5
+# .1.3.6.1.2.1.15.3.1.14.192.168.254.2 = Hex-STRING: 04 00
+# .1.3.6.1.2.1.15.3.1.16.192.168.254.2 = Gauge32: 6586822
+#
+
+import time
+from typing import Dict, List, Generator
+
+from cmk.agent_based.v2 import (
+    CheckPlugin,
+    CheckResult,
+    DiscoveryResult,
+    GetRateError,
+    Metric,
+    Result,
+    SNMPSection,
+    SNMPTree,
+    Service,
+    State,
+    StringTable,
+    check_levels,
+    exists,
+    get_rate,
+    get_value_store,
+    render,
+)
+
+from cmk_addons.plugins.bgp_peer.lib.bgp_peer import (
+    BgpPeer,
+    bgp_adminstate,
+    bgp_get_peer_entry,
+    bgp_peerstate,
+)
+
+
+def parse_bgp_peer(string_table: List[StringTable]) -> Dict[str, BgpPeer] | None:
+    peer_table = {}
+    try:
+        peer_info, local_info = string_table
+    except ValueError:
+        return None
+
+    try:
+        local_as, local_id = local_info[0]
+    except (IndexError, ValueError):
+        local_as, local_id = None, None
+
+    for entry in peer_info:
+        remote_addr, remote_id, peer_state, admin_state, local_addr, remote_as, in_updates, out_updates, \
+            in_messages, out_messages, fsm_established_transitions, fsm_established_time, in_update_elapsed_time = entry
+
+        bgp_peer = bgp_get_peer_entry(
+            admin_state=admin_state,
+            fsm_established_time=fsm_established_time,
+            fsm_established_transitions=fsm_established_transitions,
+            in_messages=in_messages,
+            in_update_elapsed_time=in_update_elapsed_time,
+            in_updates=in_updates,
+            local_addr=local_addr,
+            out_messages=out_messages,
+            out_updates=out_updates,
+            peer_state=peer_state,
+            remote_addr=remote_addr,
+            remote_as=remote_as,
+            remote_id=remote_id,
+            local_as=local_as,
+            local_id=local_id,
+        )
+        if bgp_peer:
+            peer_table.update(bgp_peer)
+    return peer_table
+
+
+def discovery_bgp_peer(params, section: Dict[str, BgpPeer]) -> DiscoveryResult:
+    _item_parts = [
+        'remote_address',
+        'remote_as',
+        'address_family',
+        'routing_instance',
+    ]
+    for key in section.keys():
+        parameters = {'internal_item': key}
+        item = ''
+        for item_part in _item_parts:
+            if item_part not in params['build_item']:
+                item += f'{section[key].item[item_part]} '
+        item = item.strip(' ')
+        yield Service(item=item, parameters=parameters)
+
+
+def bgp_peer_base_info(peer: BgpPeer) -> Generator:
+    for message, value in [
+        ('Local AS:', peer.local_as),
+        ('Local address:', peer.local_addr),
+        ('Local identifier:', peer.local_id),
+        ('Remote AS:', peer.remote_as),
+        ('Remote address:', peer.remote_addr),
+        ('Remote identifier:', peer.remote_id),
+    ]:
+        if value is not None:
+            yield Result(state=State.OK, notice=f'{message} {value}')
+
+
+def _rewrite_ruleset_needed(params: dict) -> bool:
+    for param, value in params.items():
+        match param:
+            case 'accepted_prefixes_lower_levels' | 'accepted_prefixes_upper_levels' | 'minuptime':
+                if isinstance(value, tuple) and len(value) == 2 \
+                        and isinstance(value[0], int) and isinstance(value[1], int):
+                    return True
+            case 'neighborstate':
+                for key in value.keys():
+                    if key in '123456':
+                        return True
+            case 'peer_list':
+                if isinstance(value[0], tuple):
+                    return True
+    return False
+
+
+def check_bgp_peer(item, params, section: Dict[str, BgpPeer]) -> CheckResult:
+    if not params.get('internal_item'):
+        yield Result(
+            state=State.WARN,
+            summary='This service is using old parameters (see details)',
+            details='This service is using old parameters. To refresh the parameters you need to do a "Tabula rasa" '
+                    '(Discover services -> Remove all and find new)',
+        )
+    else:
+        item = params['internal_item']
+
+    if _rewrite_ruleset_needed(params):
+        yield Result(state=State.WARN,
+                     summary='BGP ruleset needs to be opened and saved to migrate the ruleset to ruleset APIv1!')
+        return
+
+    neighbor_state = {
+        '1': 2,  # idle
+        '2': 1,  # connect
+        '3': 1,  # active
+        '4': 1,  # opensent
+        '5': 1,  # openconfirm
+        '6': 0,  # established
+    }
+
+    neighbor_state.update(params['neighborstate'])
+    peer_not_found_state = params['peernotfound']
+
+    for entry in params.get('peer_list', []):
+        if item == entry['bgp_peer']:
+            if entry.get('bgp_peer_alias'):
+                yield Result(state=State.OK, summary=f'[{entry["bgp_peer_alias"]}]', details=' ')
+            peer_not_found_state = entry.get('state_not_found', params['peernotfound'])
+
+    # get item data
+    try:
+        peer = section[item]
+    except KeyError:
+        yield Result(state=State(peer_not_found_state), summary='Item not found in SNMP data')
+        return
+
+    if peer.description:
+        yield Result(state=State.OK, summary=f'[{peer.description}]', details=' ')
+
+    if peer.admin_state == 1:  # shutdown
+        yield Result(state=State(params['admindown']), summary=f'Admin state: {bgp_adminstate(peer.admin_state)}')
+        yield from bgp_peer_base_info(peer)
+        return
+
+    yield Result(
+        state=State(neighbor_state.get(str(peer.peer_state))),
+        notice=f'Peer state: {bgp_peerstate(peer.peer_state)}'
+    )
+
+    if peer.fsm_established_time:  # always yield fsm_established time
+        yield Metric(
+            name=f'bgp_peer_fsmestablishedtime',
+            value=peer.fsm_established_time,
+            boundaries=(0, None)
+        )
+
+    if not peer.peer_state == 6:  # not established
+        yield from bgp_peer_base_info(peer)
+        return
+
+    if peer.fsm_established_time:  # if peer is established use fsm_established_time as uptime
+        yield from check_levels(
+            value=peer.fsm_established_time,
+            label='Uptime',
+            levels_upper=('no_levels', None),
+            levels_lower=params['minuptime'],
+            render_func=render.timespan,
+            metric_name='bgp_peer_fsmestablishedtime',
+        )
+
+    if peer.peer_unavail_reason != 0:  # huawei peer unavailable state
+        yield Result(state=State.CRIT, notice=F'Peer unavailable reason: {peer.peer_unavail_reason_str}')
+
+    if peer.device_admin_limit and peer.prefix_admin_limit is None:
+        yield Result(
+            state=State(params['noprefixlimit']),
+            notice='Prefix limit/warn threshold not configured on the device.',
+        )
+
+    if peer.accepted_prefixes is not None and peer.peer_state == 6:  # peer established and prefixes accepted
+        if peer.prefix_admin_limit is None or peer.prefix_threshold is None:
+            levels_upper = None
+        else:
+            # use float (100.0) to get xx.xx in division
+            levels_upper = (
+                'fixed', (int(peer.prefix_admin_limit / 100.0 * peer.prefix_threshold), peer.prefix_admin_limit)
+            )
+
+        yield from check_levels(
+            value=peer.accepted_prefixes,
+            metric_name='bgp_peer_acceptedprefixes',
+            levels_upper=params.get('accepted_prefixes_upper_levels', levels_upper),
+            levels_lower=params.get('accepted_prefixes_lower_levels'),
+            label='Prefixes accepted',
+            render_func=lambda v: f'{v}'
+        )
+
+    now_time = time.time()
+    value_store = get_value_store()
+
+    for key, value in peer.metric_rate:
+        try:
+            value = get_rate(value_store, f'{key}', now_time, value, raise_overflow=True)
+        except GetRateError:
+            continue
+        yield Metric(name=f'bgp_peer_{key}', value=value, boundaries=(0, None))
+
+    for key, value in peer.metric_count:
+        if value is not None:
+            yield Metric(name=f'bgp_peer_{key}', value=value, boundaries=(0, None))
+
+    yield from bgp_peer_base_info(peer)
+
+
+snmp_section_bgp_peer = SNMPSection(
+    name='bgp_peer',
+    parse_function=parse_bgp_peer,
+    fetch=[
+        SNMPTree(
+            base='.1.3.6.1.2.1.15.3.1',  # BGP4-MIB::BgpPeerEntry
+            oids=[
+                '7',  # bgpPeerRemoteAddr
+                '1',  # bgpPeerIdentifier
+                '2',  # bgpPeerState
+                '3',  # bgpPeerAdminStatus
+                '5',  # bgpPeerLocalAddr
+                '9',  # bgpPeerRemoteAs
+                '10',  # bgpPeerInUpdates
+                '11',  # bgpPeerOutUpdates
+                '12',  # bgpPeerInTotalMessages
+                '13',  # bgpPeerOutTotalMessages
+                '15',  # bgpPeerFsmEstablishedTransitions
+                '16',  # bgpPeerFsmEstablishedTime
+                '24',  # bgpPeerInUpdateElapsedTime
+            ]),
+        SNMPTree(
+            base='.1.3.6.1.2.1.15',  # BGP4-MIB
+            oids=[
+                '2',  # bgpLocalAs
+                '4',  # bgpIdentifier (local)
+            ])
+    ],
+    detect=exists('.1.3.6.1.2.1.15.3.1.1.*')
+)
+
+check_plugin_bgp_peer = CheckPlugin(
+    name='bgp_peer',
+    service_name='BGP peer %s',
+    discovery_function=discovery_bgp_peer,
+    discovery_default_parameters={
+        'build_item': [
+            # 'remote_address',
+            'remote_as',
+            # 'address_family',
+            # 'routing_instance',
+        ]
+    },
+    discovery_ruleset_name='discovery_bgp_peer',
+    check_function=check_bgp_peer,
+    check_default_parameters={
+        'minuptime': ('fixed', (7200, 3600)),
+        'peer_list': [],
+        'peernotfound': 2,
+        'admindown': 1,
+        'noprefixlimit': 1,
+        'neighborstate': {
+            'idle': 2,  # ........1
+            'connect': 1,  # .....2
+            'active': 1,  # ......3
+            'open_sent': 1,  # ...4
+            'open_confirm': 1,  # 5
+            'established': 0,  # .6
+        },
+    },
+    check_ruleset_name='bgp_peer',
+)
diff --git a/source/agent_based/inv_bgp_peer.py b/source/cmk_addons_plugins/bgp_peer/agent_based/inv_bgp_peer.py
similarity index 94%
rename from source/agent_based/inv_bgp_peer.py
rename to source/cmk_addons_plugins/bgp_peer/agent_based/inv_bgp_peer.py
index 869b3375b22d292caa3bd1e0cb1d7ab0280c3088..ce96d0771be2f288afc86212edf6518593ec3258 100644
--- a/source/agent_based/inv_bgp_peer.py
+++ b/source/cmk_addons_plugins/bgp_peer/agent_based/inv_bgp_peer.py
@@ -18,18 +18,18 @@
 import time
 from typing import List
 
-from cmk.base.plugins.agent_based.agent_based_api.v1 import (
+from cmk.agent_based.v2 import (
+    InventoryPlugin,
+    InventoryResult,
     OIDBytes,
+    SNMPSection,
     SNMPTree,
+    StringByteTable,
     TableRow,
     exists,
-    register,
-)
-from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    InventoryResult,
-    StringByteTable,
 )
-from cmk.base.plugins.agent_based.utils.bgp_peer import (
+
+from cmk_addons.plugins.bgp_peer.lib.bgp_peer import (
     BgpWhois,
     InvBgpPeer,
     bgp_error_as_string,
@@ -132,7 +132,7 @@ def inventory_bgp_peers(params, section) -> InventoryResult:
         del whois
 
 
-register.snmp_section(
+snmp_section_inv_bgp_peer = SNMPSection(
     name='inv_bgp_peer',
     parse_function=parse_inv_bgp_peer,
     fetch=[
@@ -160,7 +160,7 @@ register.snmp_section(
     detect=exists('.1.3.6.1.2.1.15.3.1.1.*')
 )
 
-register.inventory_plugin(
+inventory_plugin_inv_bgp_peer = InventoryPlugin(
     name='inv_bgp_peer',
     inventory_function=inventory_bgp_peers,
     inventory_default_parameters={
diff --git a/source/checkman/bgp_peer b/source/cmk_addons_plugins/bgp_peer/checkman/bgp_peer
similarity index 100%
rename from source/checkman/bgp_peer
rename to source/cmk_addons_plugins/bgp_peer/checkman/bgp_peer
diff --git a/source/cmk_addons_plugins/bgp_peer/graphing/bgp_peer.py b/source/cmk_addons_plugins/bgp_peer/graphing/bgp_peer.py
new file mode 100644
index 0000000000000000000000000000000000000000..b40ffd9f23bacf6e6168aa8b6676a5ddf6dda332
--- /dev/null
+++ b/source/cmk_addons_plugins/bgp_peer/graphing/bgp_peer.py
@@ -0,0 +1,290 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# License: GNU General Public License v2
+#
+# Author: thl-cmk[at]outlook[dot]com
+# URL   : https://thl-cmk.hopto.org
+# Date  : 2021-08-21
+#
+# BGP Peer metrics plugin
+#
+# 2024-05-17: fixed typo in range mac -> max (crashed graphs in cmk 2.3.)
+# 2025-03-29: rewritten for graphing API v1
+
+from cmk.graphing.v1 import Title, graphs, metrics, perfometers
+
+
+UNIT_COUNTER = metrics.Unit(metrics.DecimalNotation(''), metrics.StrictPrecision(2))
+UNIT_PER_SECOND = metrics.Unit(metrics.DecimalNotation('/s'))
+UNIT_TIME = metrics.Unit(metrics.TimeNotation())
+
+metric_bgp_peer_accepted_prefixes = metrics.Metric(
+    name='bgp_peer_acceptedprefixes',
+    title=Title("Prefixes accepted"),
+    unit=UNIT_COUNTER,
+    color=metrics.Color.DARK_PURPLE,
+)
+
+metric_bgp_peer_advertised_prefixes = metrics.Metric(
+    name='bgp_peer_advertisedprefixes',
+    title=Title("Prefixes advertised"),
+    unit=UNIT_PER_SECOND,
+    color=metrics.Color.CYAN,
+)
+
+metric_bgp_peer_denied_prefixes = metrics.Metric(
+    name='bgp_peer_deniedprefixes',
+    title=Title("Prefixes denied"),
+    unit=UNIT_PER_SECOND,
+    color=metrics.Color.YELLOW,
+)
+
+metric_bgp_peer_fsm_established_time = metrics.Metric(
+    name='bgp_peer_fsmestablishedtime',
+    title=Title("FSM last change"),
+    unit=UNIT_TIME,
+    color=metrics.Color.GREEN,
+)
+
+metric_bgp_peer_fsm_established_transitions = metrics.Metric(
+    name='bgp_peer_fsmestablishedtransitions',
+    title=Title("FSM transitions"),
+    unit=UNIT_COUNTER,
+    color=metrics.Color.YELLOW,
+)
+
+metric_bgp_peer_in_total_messages = metrics.Metric(
+    name='bgp_peer_intotalmessages',
+    title=Title("Messages received"),
+    unit=UNIT_PER_SECOND,
+    color=metrics.Color.DARK_BLUE,
+)
+
+metric_bgp_peer_in_update_elapsed_time = metrics.Metric(
+    name='bgp_peer_inupdateelapsedtime',
+    title=Title("Last update received"),
+    unit=UNIT_TIME,
+    color=metrics.Color.DARK_BLUE,
+)
+
+metric_bgp_peer_in_updates = metrics.Metric(
+    name='bgp_peer_inupdates',
+    title=Title("Updates received"),
+    unit=UNIT_PER_SECOND,
+    color=metrics.Color.YELLOW,
+)
+
+metric_bgp_peer_out_total_messages = metrics.Metric(
+    name='bgp_peer_outtotalmessages',
+    title=Title("Messages send"),
+    unit=UNIT_PER_SECOND,
+    color=metrics.Color.DARK_PINK,
+)
+
+metric_bgp_peer_out_updates = metrics.Metric(
+    name='bgp_peer_outupdates',
+    title=Title("Updates send"),
+    unit=UNIT_PER_SECOND,
+    color=metrics.Color.CYAN,
+)
+
+metric_bgp_peer_suppressed_prefixes = metrics.Metric(
+    name='bgp_peer_suppressedprefixes',
+    title=Title("Prefixes suppressed"),
+    unit=UNIT_PER_SECOND,
+    color=metrics.Color.DARK_PINK,
+)
+
+metric_bgp_peer_withdrawn_prefixes = metrics.Metric(
+    name='bgp_peer_withdrawnprefixes',
+    title=Title("Prefixes withdrawn"),
+    unit=UNIT_PER_SECOND,
+    color=metrics.Color.BLUE,
+)
+
+# Juniper specific metrics
+metric_bgp_peer_in_prefixes = metrics.Metric(
+    name='bgp_peer_in_prefixes',
+    title=Title("Prefixes in"),
+    unit=UNIT_COUNTER,
+    color=metrics.Color.DARK_PURPLE,
+)
+
+metric_bgp_peer_in_prefixes_rejected = metrics.Metric(
+    name='bgp_peer_in_prefixes_rejected',
+    title=Title("Prefixes in rejected"),
+    unit=UNIT_COUNTER,
+    color=metrics.Color.YELLOW,
+)
+
+metric_bgp_peer_in_prefixes_active = metrics.Metric(
+    name='bgp_peer_in_prefixes_active',
+    title=Title("Prefixes in active"),
+    unit=UNIT_COUNTER,
+    color=metrics.Color.CYAN,
+)
+
+metric_bgp_peer_out_prefixes = metrics.Metric(
+    name='bgp_peer_out_prefixes',
+    title=Title("Prefixes out"),
+    unit=UNIT_COUNTER,
+    color=metrics.Color.BLUE,
+)
+
+# Huawei specific metrics
+metric_bgp_peer_prefix_adv_counter = metrics.Metric(
+    name='bgp_peer_prefixadvcounter',
+    title=Title("Prefixes advertised"),
+    unit=UNIT_COUNTER,
+    color=metrics.Color.DARK_BLUE,
+)
+
+metric_bgp_peer_prefix_active_counter = metrics.Metric(
+    name='bgp_peer_prefixactivecounter',
+    title=Title("Prefixes active"),
+    unit=UNIT_COUNTER,
+    color=metrics.Color.CYAN,
+)
+
+metric_bgp_peer_prefix_rcv_counter = metrics.Metric(
+    name='bgp_peer_prefixrcvcounter',
+    title=Title("Prefixes received"),
+    unit=UNIT_COUNTER,
+    color=metrics.Color.DARK_PURPLE,
+)
+
+graph_bgp_peer_fms_transitions_last_change = graphs.Graph(
+    name='bgp_peer.fms_transitions_last_change',
+    title=Title("FSM established last change"),
+    minimal_range=graphs.MinimalRange(0, metrics.MaximumOf('bgp_peer_fsmestablishedtime', metrics.Color.GRAY, ), ),
+    compound_lines=['bgp_peer_fsmestablishedtime'],
+)
+
+graph_bgp_peer_prefixes_accepted = graphs.Graph(
+    name='bgp_peer.prefixes_accepted',
+    title=Title("Accepted Prefixes"),
+    minimal_range=graphs.MinimalRange(0, metrics.MaximumOf('bgp_peer_acceptedprefixes', metrics.Color.GRAY, ), ),
+    compound_lines=['bgp_peer_acceptedprefixes'],
+    simple_lines=[
+        metrics.CriticalOf('bgp_peer_acceptedprefixes'),
+        metrics.WarningOf('bgp_peer_acceptedprefixes'),
+    ],
+)
+
+graph_bgp_peer_prefixes_per_second = graphs.Graph(
+    name='bgp_peer.prefixes_per_second',
+    title=Title("Prefixes/s"),
+    simple_lines=[
+        'bgp_peer_withdrawnprefixes',
+        'bgp_peer_suppressedprefixes',
+        'bgp_peer_deniedprefixes',
+        'bgp_peer_advertisedprefixes',
+    ],
+    optional=[
+        'bgp_peer_withdrawnprefixes',
+        'bgp_peer_suppressedprefixes',
+        'bgp_peer_deniedprefixes',
+        'bgp_peer_advertisedprefixes',
+    ],
+)
+
+graph_bgp_peer_updates_in_out = graphs.Bidirectional(
+    name='bgp_peer.updates_in_out',
+    title=Title("Updates"),
+    lower=graphs.Graph(
+        name='bgp_peer.updates_in_out_lower',
+        title=Title("Updates"),
+        compound_lines=['bgp_peer_outupdates'],
+    ),
+    upper=graphs.Graph(
+        name='bgp_peer.updates_in_out_upper',
+        title=Title("Updates"),
+        compound_lines=['bgp_peer_inupdates'],
+    ),
+)
+
+graph_bgp_peer_messages_in_out = graphs.Bidirectional(
+    name='bgp_peer.messages_in_out',
+    title=Title("Total messages"),
+    lower=graphs.Graph(
+        name='bgp_peer.messages_in_out_lower',
+        title=Title("Total messages"),
+        compound_lines=['bgp_peer_outtotalmessages'],
+    ),
+    upper=graphs.Graph(
+        name='bgp_peer.messages_in_out_upper',
+        title=Title("Total messages"),
+        compound_lines=['bgp_peer_intotalmessages'],
+    ),
+)
+
+graph_bgp_peer_fms_transitions_from_to = graphs.Graph(
+    name='bgp_peer.fms_transitions_from_to',
+    title=Title("FSM transitions from/to established"),
+    minimal_range=graphs.MinimalRange(
+        0, metrics.MaximumOf('bgp_peer_fsmestablishedtransitions', metrics.Color.GRAY, ),
+    ),
+    compound_lines=['bgp_peer_fsmestablishedtransitions'],
+)
+
+graph_bgp_peer_time_since_last_update = graphs.Graph(
+    name='bgp_peer.time_since_last_update',
+    title=Title("Time since last update received"),
+    minimal_range=graphs.MinimalRange(
+        0, metrics.MaximumOf('bgp_peer_inupdateelapsedtime', metrics.Color.GRAY, ),
+    ),
+    compound_lines=['bgp_peer_inupdateelapsedtime'],
+)
+
+# juniper prefixes
+graph_bgp_peer_juniper_prefixes = graphs.Bidirectional(
+    name='bgp_peer.juniper_prefixes',
+    title=Title("Prefixes in/out"),
+    lower=graphs.Graph(
+        name='bgp_peer.juniper_prefixes_lower',
+        title=Title("Prefixes in/out"),
+        simple_lines=['bgp_peer_out_prefixes'],
+    ),
+    upper=graphs.Graph(
+        name='bgp_peer.juniper_prefixes_upper',
+        title=Title("Prefixes in/out"),
+        simple_lines=[
+            'bgp_peer_in_prefixes_rejected',
+            'bgp_peer_in_prefixes_active',
+            'bgp_peer_in_prefixes',
+        ],
+    ),
+)
+
+# huawei prefixes
+graph_huawei_bgp_peer_counter = graphs.Graph(
+    name='huawei_bgp_peer_counter',
+    title=Title("BGP prefix counter"),
+    simple_lines=[
+        'bgp_peer_prefixrcvcounter',
+        'bgp_peer_prefixactivecounter',
+        'bgp_peer_prefixadvcounter',
+    ],
+)
+
+
+perfometer_bgp_peer_accepted_prefixes = perfometers.Stacked(
+    name='bgp_peer_stacked',
+    upper=perfometers.Perfometer(
+        name='bgp_peer_fsmestablishedtime',
+        segments=['bgp_peer_fsmestablishedtime'],
+        focus_range=perfometers.FocusRange(perfometers.Closed(0), perfometers.Open(2592000)) # ome month
+    ),
+    lower=perfometers.Perfometer(
+        name='bgp_peer_acceptedprefixes',
+        segments=['bgp_peer_acceptedprefixes'],
+        focus_range=perfometers.FocusRange(perfometers.Closed(0), perfometers.Open(500000))
+    )
+)
+
+perfometer_bgp_peer_fsm_established_time = perfometers.Perfometer(
+    name='bgp_peer_fsmestablishedtime',
+    segments=['bgp_peer_fsmestablishedtime'],
+    focus_range=perfometers.FocusRange(perfometers.Closed(0), perfometers.Open(2592000)) # ome month
+)
\ No newline at end of file
diff --git a/source/agent_based/utils/bgp_peer.py b/source/cmk_addons_plugins/bgp_peer/lib/bgp_peer.py
similarity index 100%
rename from source/agent_based/utils/bgp_peer.py
rename to source/cmk_addons_plugins/bgp_peer/lib/bgp_peer.py
diff --git a/source/cmk_addons_plugins/bgp_peer/rulesets/bgp_peer.py b/source/cmk_addons_plugins/bgp_peer/rulesets/bgp_peer.py
new file mode 100644
index 0000000000000000000000000000000000000000..fe1df4b39dfb2b44baa5f35f628109b980d61ee1
--- /dev/null
+++ b/source/cmk_addons_plugins/bgp_peer/rulesets/bgp_peer.py
@@ -0,0 +1,336 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# License: GNU General Public License v2
+#
+# Author: thl-cmk[at]outlook[dot]com
+# URL   : https://thl-cmk.hopto.org
+# Date  : 2017-12-25
+#
+# Check_MK bgp_peers WATO plugin
+#
+# 2021-03-27: rewrite for CMK 2.0
+# 2021-08-21: modified for bgp_peer plugin (from cisco_bgp_peer)
+# 2021-08-29: removed htmloutput and infotext_values option
+# 2022-04-02: added bgp neighbour states
+# 2022-04-29: added upper/lower prefix limit
+# 2022-05-09: added discovery rule set
+# 2022-05-11: added remote_as to build_item
+# 2022-09-05: added internal_item to avoid warnings on cmk updates (THX to Jay2k1 for reporting the issue)
+# 2023-06-11: moved wato file from ~local/lib/check_mk/gui/plugins/wato
+#             to ~/local/lib/check_mk/gui/plugins/wato/check_parameters to override the build in wato plugin
+# 2024-06-15: fixed typo (no not)
+#
+# Known issue: the override of the build in wato plugin will break the build in aritsa bgp peer plugin
+from click import help_option
+from cmk.rulesets.v1 import Help, Label, Title
+from cmk.rulesets.v1.form_specs import (
+    DefaultValue,
+    DictElement,
+    Dictionary,
+    InputHint,
+    Integer,
+    LevelDirection,
+    LevelsType,
+    List,
+    MultipleChoice,
+    MultipleChoiceElement,
+    ServiceState,
+    SimpleLevels,
+    String,
+    TimeMagnitude,
+    TimeSpan,
+    migrate_to_integer_simple_levels,
+)
+from cmk.rulesets.v1.rule_specs import CheckParameters, DiscoveryParameters, Topic, HostAndItemCondition
+from cmk.rulesets.v1.form_specs.validators import NumberInRange, Message, LengthInRange
+
+item_parts = [
+    # MultipleChoiceElement(name='remote_address', title=Title('Peer remote address')),
+    MultipleChoiceElement(name='remote_as', title=Title('Remote AS')),
+    MultipleChoiceElement(name='address_family', title=Title('Address family')),
+    MultipleChoiceElement(name='routing_instance', title=Title('Routing instance/VRF')),
+]
+
+def _migrate_to_float(value: object) -> float:
+    if isinstance(value, int | float):
+        return float(value)
+
+    raise TypeError(value)
+
+def _migrate_bgp_peer_list(value: object) -> list:
+    if not isinstance(value, list):
+        raise TypeError(value)
+
+    peer_list = []
+
+    for entry in value:
+        if isinstance(entry, tuple):
+            bgp_peer, bgp_peer_alias, state_not_found = entry
+            peer_list.append({
+                'bgp_peer': bgp_peer,
+                'bgp_peer_alias': bgp_peer_alias,
+                'state_not_found': state_not_found
+            })
+        else:
+            peer_list.append(entry)
+
+    return peer_list
+
+
+def _migrate_neighbor_state(value: object) -> dict:
+    if not isinstance(value, dict):
+        raise TypeError(value)
+
+    state_map = {}
+
+    for state, value in value.items():
+        match state:
+            case '1':
+                state_map['idle'] = value
+            case '2':
+                state_map['connect'] = value
+            case '3':
+                state_map['active'] = value
+            case '4':
+                state_map['open_sent'] = value
+            case '5':
+                state_map['open_confirm'] = value
+            case '6':
+                state_map['established'] = value
+            case _:
+                state_map[state] = value
+
+    return state_map
+
+def _parameter_form_check_bgp_peer():
+    return Dictionary(
+        elements={
+            'minuptime': DictElement(
+                parameter_form=SimpleLevels(
+                    title=Title('Minimum uptime for peer'),
+                    help_text=Help('Set the time in seconds, a peer must be up before the peer is considered sable.'),
+                    prefill_levels_type=DefaultValue(LevelsType.FIXED),
+                    prefill_fixed_levels=InputHint((7200, 3600)),
+                    level_direction=LevelDirection.LOWER,
+                    migrate=migrate_to_integer_simple_levels,
+                    form_spec_template=TimeSpan(
+                        displayed_magnitudes=[TimeMagnitude.SECOND, TimeMagnitude.MINUTE, TimeMagnitude.HOUR],
+                        custom_validate=(NumberInRange(min_value=0),),
+                        migrate=_migrate_to_float,
+                    ))),
+            'accepted_prefixes_upper_levels': DictElement(
+                parameter_form=SimpleLevels(
+                    title=Title('Accepted prefixes upper levels'),
+                    help_text=Help('The values from WATO are preferred to the values from the device.'),
+                    prefill_levels_type=DefaultValue(LevelsType.FIXED),
+                    prefill_fixed_levels=InputHint((0, 0)),
+                    level_direction=LevelDirection.UPPER,
+                    migrate=migrate_to_integer_simple_levels,
+                    form_spec_template=Integer(
+                        unit_symbol='prefixes',
+                        custom_validate=(NumberInRange(min_value=0, ),)
+                    ))),
+            'accepted_prefixes_lower_levels': DictElement(
+                parameter_form=SimpleLevels(
+                    title=Title('Accepted prefixes lower levels'),
+                    help_text=Help('The values from WATO are preferred to the values from the device.'),
+                    prefill_levels_type=DefaultValue(LevelsType.FIXED),
+                    prefill_fixed_levels=InputHint((0, 0)),
+                    level_direction=LevelDirection.LOWER,
+                    migrate=migrate_to_integer_simple_levels,
+                    form_spec_template=Integer(
+                        unit_symbol='prefixes',
+                        custom_validate=(NumberInRange(min_value=0, ),),
+                    ))),
+            'peernotfound': DictElement(
+                parameter_form=ServiceState(
+                    title=Title('State if peer is not found.'),
+                    help_text=Help('Default monitoring state if the peer is not found in the SNMP data'),
+                    prefill=DefaultValue(2)
+                )),
+            'admindown': DictElement(
+                parameter_form=ServiceState(
+                    prefill=DefaultValue(1),
+                    title=Title('State if peer is admin shutdown.'),
+                    help_text=Help('Monitoring state if the peer is admin shutdown')
+                )),
+
+            'neighborstate': DictElement(
+                parameter_form=Dictionary(
+                    title=Title('State to report for BGP neighbor state'),
+                    help_text=Help('Map each BGP state to a CheckMK monitoring state'),
+                    migrate=_migrate_neighbor_state,
+                    elements={
+                        'idle': DictElement(
+                            parameter_form=ServiceState(
+                                title=Title('1 - idle'),
+                                help_text=Help(
+                                    'This is the first stage of the BGP FSM. BGP detects a start event, tries to initiate a '
+                                    'TCP connection to the BGP peer, and also listens for a new connect from a peer router. '
+                                    'If an error causes BGP to go back to the Idle state for a second time, the '
+                                    'ConnectRetryTimer is set to 60 seconds and must decrement to zero before the connection '
+                                    'is initiated again. Further failures to leave the Idle state result in the '
+                                    'ConnectRetryTimer doubling in length from the previous time. '
+                                    'Default monitoring state is "CRIT"'
+                                ),
+                                prefill=DefaultValue(2)
+                            )),
+                        'connect': DictElement(
+                            parameter_form=ServiceState(
+                                title=Title('2 - connect'),
+                                help_text=Help(
+                                    'In this state, BGP initiates the TCP connection. If the 3-way TCP handshake completes, '
+                                    'the established BGP Session BGP process resets the ConnectRetryTimer and sends the Open '
+                                    'message to the neighbor, and then changes to the OpenSent State.'
+                                    'Default monitoring state is "WARN"'),
+                                prefill=DefaultValue(1),
+                            )),
+                        'active': DictElement(
+                            parameter_form=ServiceState(
+                                title=Title('3 - active'),
+                                help_text=Help(
+                                    'In this state, BGP starts a new 3-way TCP handshake. If a connection is established, '
+                                    'an Open message is sent, the Hold Timer is set to 4 minutes, and the state moves to '
+                                    'OpenSent. If this attempt for TCP connection fails, the state moves back to the Connect '
+                                    'state and resets the ConnectRetryTimer. '
+                                    'Default monitoring state is "WARN"'),
+                                prefill=DefaultValue(1),
+                            )),
+                        'open_sent': DictElement(
+                            parameter_form=ServiceState(
+                                title=Title('4 - opensent'),
+                                help_text=Help(
+                                    'In this state, an Open message has been sent from the originating router and is awaiting '
+                                    'an Open message from the other router. After the originating router receives the OPEN '
+                                    'message from the other router, both OPEN messages are checked for errors. If the Open '
+                                    'messages do not have any errors, the Hold Time is negotiated (using the lower value), '
+                                    'and a KEEPALIVE message is sent (assuming the value is not set to zero). The connection '
+                                    'state is then moved to OpenConfirm. If an error is found in the OPEN message, a '
+                                    'Notification message is sent, and the state is moved back to Idle.'
+                                    ' Default monitoring state is "WARN"'),
+                                prefill=DefaultValue(1),
+                            )),
+                        'open_confirm': DictElement(
+                            parameter_form=ServiceState(
+                                title=Title('5 - openconfirm'),
+                                help_text=Help(
+                                    'In this state, BGP waits for a Keepalive or Notification message. Upon receipt of a '
+                                    'neighbor’s Keepalive, the state is moved to Established. If the hold timer expires, a '
+                                    'stop event occurs, or a Notification message is received, and the state is moved to '
+                                    'Idle. '
+                                    'Default monitoring state is "WARN"'),
+                                prefill=DefaultValue(1),
+                            )),
+                        'established': DictElement(
+                            parameter_form=ServiceState(
+                                title=Title('6 - established'),
+                                help_text=Help(
+                                    'In this state, the BGP session is established. BGP neighbors exchange routes via Update '
+                                    'messages. As Update and Keepalive messages are received, the Hold Timer is reset. If the '
+                                    'Hold Timer expires, an error is detected and BGP moves the neighbor back to the Idle '
+                                    'state. '
+                                    'Default monitoring state is "OK"'),
+                                prefill=DefaultValue(0),
+                            )),
+                    }
+                )),
+
+            'noprefixlimit': DictElement(
+                parameter_form=ServiceState(
+                    title=Title('State if no admin prefix limit/warn threshold is configured.'),
+                    help_text=Help(
+                        'The admin prefix limit and warn threshold needs to be configured on the device. '
+                        'For example: "neighbor 172.17.10.10 maximum-prefix 10000 80". The threshold is in percentage '
+                        'of the prefix limit.'
+                    ),
+                    prefill=DefaultValue(1),
+                )),
+            'peer_list': DictElement(
+                parameter_form=List(
+                    title=Title('BGP Peers'),
+                    add_element_label=Label('Add BGP peer'),
+                    remove_element_label=Label('remove BGP peer'),
+                    no_element_label=Label('Add at least one BGP peer'),
+                    migrate=_migrate_bgp_peer_list,
+                    element_template=Dictionary(
+                        elements={
+                            'bgp_peer': DictElement(
+                                required=True,
+                                parameter_form=String(
+                                    title=Title('BGP peer'),
+                                    help_text=Help(
+                                        'The configured value must match a BGP item reported by the monitored '
+                                        'device. For example: "10.194.115.98" or "2A10:1CD0:1020:135::20 IPv6 Unicast"'
+                                    ),
+                                    custom_validate=(LengthInRange(min_value=1, ),),
+                                )),
+                            'bgp_peer_alias': DictElement(
+                                parameter_form=String(
+                                    title=Title('BGP Peer Alias'),
+                                    help_text=Help(
+                                        'You can configure an individual alias here for the BGP peer matching '
+                                        'the text configured in the "BGP Peer IP-address" field. The alias will '
+                                        'be shown in the check info'
+                                    ),
+                                    custom_validate=(LengthInRange(min_value=1, ),),
+                                )),
+                            'state_not_found': DictElement(
+                                required=True,
+                                parameter_form=ServiceState(
+                                    title=Title('State if not found'),
+                                    help_text=Help(
+                                        'You can configure an individual state if the BGP peer matching the text '
+                                        'configured in the "BGP Peer IP-address" field is not found'
+                                    ),
+                                    prefill=DefaultValue(2),
+                                )),
+                        }
+                    )
+                )),
+            'internal_item': DictElement(
+                render_only=True,
+                parameter_form=MultipleChoice(
+                    title=Title('Information not to use in the item name'),
+                    elements=item_parts,
+                )),
+        }
+    )
+
+
+rule_spec_check_bgp_peer = CheckParameters(
+    name='bgp_peer',
+    parameter_form=_parameter_form_check_bgp_peer,
+    title=Title('BGP Peer'),
+    topic=Topic.NETWORKING,
+    condition=HostAndItemCondition(item_title=Title('BGP peer')),
+)
+
+
+def _parameter_form_discovery_bgp_peer():
+    return Dictionary(
+        elements={
+            'build_item': DictElement(
+                parameter_form=MultipleChoice(
+                    title=Title('Information not to use in the item name'),
+                    help_text=Help(
+                        'The Peer remote address is always used as the item name. By default the check will add the '
+                        'address-family and the routing instance/VRF if available. You can decide to not use these '
+                        'additional information in the item name. Do so only if your peers have only one address-'
+                        'family configured and you don\'t have the same peer remote address in different routing '
+                        'instances/VRFs configured.'
+                    ),
+                    elements=item_parts,
+                    prefill=DefaultValue(['remote_as'])
+                )
+            )
+        }
+    )
+
+
+rule_spec_discovery_bgp_beer = DiscoveryParameters(
+    title=Title('BGP peer'),
+    topic=Topic.NETWORKING,
+    name='discovery_bgp_peer',
+    parameter_form=_parameter_form_discovery_bgp_peer,
+)
diff --git a/source/cmk_addons_plugins/bgp_peer/rulesets/inv_bgp_peer.py b/source/cmk_addons_plugins/bgp_peer/rulesets/inv_bgp_peer.py
new file mode 100644
index 0000000000000000000000000000000000000000..f1d30bc35f50ae358697a25a6c573be9579bd207
--- /dev/null
+++ b/source/cmk_addons_plugins/bgp_peer/rulesets/inv_bgp_peer.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Author: thl-cmk[at]outlook[dot]com
+# URL   : https://thl-cmk.hopto.org
+# Date  : 2022-04-24
+#
+# 2022-04-24: added option for BGP down time
+#             added option to remove some columns from inventory
+# 2022-04-28: added Whois options
+# 2023-06-12: moved wato file from ~local/lib/check_mk/gui/plugins/wato
+#             to ~/local/lib/check_mk/gui/plugins/wato/check_parameters
+# 2025-03-29: rewritten for rulesets API v1
+
+from cmk.rulesets.v1 import Title, Help
+from cmk.rulesets.v1.form_specs import (
+    DefaultValue,
+    DictElement,
+    Dictionary,
+    Integer,
+    MultipleChoice,
+    MultipleChoiceElement,
+    SingleChoice,
+    SingleChoiceElement,
+    TimeMagnitude,
+    TimeSpan,
+)
+from cmk.rulesets.v1.rule_specs import InventoryParameters, Topic
+from cmk.rulesets.v1.form_specs.validators import NumberInRange, Message
+
+def _migrate_to_float(value: object) -> float:
+    if isinstance(value, int | float):
+        return float(value)
+
+    raise TypeError(value)
+
+
+def _parameter_form_inv_bgp_peer():
+    remove_columns = [
+        # MultipleChoiceElement(name='remote_as', title=Title('Remote AS')),
+        # MultipleChoiceElement(name='remote_id', title=Title('Remote ID')),
+        # MultipleChoiceElement(name='local_addr', title=Title('Local address')),
+        # MultipleChoiceElement(name='local_as', title=Title('Local AS')),
+        # MultipleChoiceElement(name='local_id', title=Title('Local ID')),
+        # MultipleChoiceElement(name='prev_state', title=Title('Previous state')),
+        MultipleChoiceElement(name='address_family', title=Title('Address family')),
+        MultipleChoiceElement(name='last_error', title=Title('Last error')),
+        MultipleChoiceElement(name='last_error_code', title=Title('Last error code')),
+        MultipleChoiceElement(name='as_name', title=Title('Remote AS name')),
+        MultipleChoiceElement(name='as_org_name', title=Title('Remote AS Org Name')),
+        MultipleChoiceElement(name='bgp_type', title=Title('Type')),
+        MultipleChoiceElement(name='version', title=Title('Version')),
+    ]
+
+    rir_s = [
+        SingleChoiceElement(name='afrinic', title=Title('AFRINIC (https://rdap.afrinic.net/rdap)')),
+        SingleChoiceElement(name='apnic', title=Title('APNIC (https://rdap.apnic.net)')),
+        SingleChoiceElement(name='arin', title=Title('ARIN (https://rdap.arin.net/registry)')),
+        SingleChoiceElement(name='ripe', title=Title('RIPE (https://rdap.db.ripe.net)')),
+        SingleChoiceElement(name='lacnic', title=Title('LACNIC (https://rdap.apnic.net)')),
+    ]
+
+    return Dictionary(
+        elements={
+            'not_in_service_time': DictElement(
+                parameter_form=TimeSpan(
+                    title=Title('Time peer is not up until considered not in service'),
+                    displayed_magnitudes=[TimeMagnitude.MINUTE, TimeMagnitude.HOUR, TimeMagnitude.DAY],
+                    prefill=DefaultValue(2592000.0),  # 30 days in seconds
+                    migrate=_migrate_to_float,
+                )),
+            'remove_columns': DictElement(
+                parameter_form=MultipleChoice(
+                    title=Title('List of columns to remove'),
+                    help_text=Help('Information to remove from inventory'),
+                    elements=remove_columns,
+                )),
+            'whois_enable': DictElement(
+                parameter_form=Dictionary(
+                    title=Title('Add whois data to the inventory'),
+                    help_text=Help(
+                        'The whois data will be fetched via RDAP from the registries. For this the the plugin tries to'
+                        'find the best registry via the RDAP bootstrap data from https://data.iana.org/rdap/asn.json.'
+                        'The query it self will go to the found registry via http(s). Note: the request might be get '
+                        'redirected if there a different authoritative registry for the ASn'
+                    ),
+                    elements={
+                        'whois_rir': DictElement(
+                            parameter_form=SingleChoice(
+                                title=Title('Preferred RIR to fetch whois data'),
+                                help_text=Help(
+                                    'This registry will be used if the plugin can not determine the authoritative registry '
+                                    'based on the bootstrap data.'
+                                ),
+                                elements=rir_s,
+                                prefill=DefaultValue('ripe'),
+                            )),
+                        'whois_timeout': DictElement(
+                            parameter_form=Integer(
+                                custom_validate=(
+                                    NumberInRange(
+                                        min_value=1,
+                                        max_value=None,
+                                        error_msg=Message('The timeout must be greater than 0')
+                                    ),
+                                ),
+                                help_text=Help('The connection timeout for each whois request.'),
+                                prefill=DefaultValue(5),
+                                title=Title('Timeout for connections to RIRs'),
+                                unit_symbol='seconds',
+                            )),
+                    }
+                )
+            )
+        }
+    )
+
+
+rule_spec_inv_bgp_peer = InventoryParameters(
+    name='inv_bgp_peer',
+    parameter_form=_parameter_form_inv_bgp_peer,
+    title=Title('BGP peer'),
+    topic=Topic.NETWORKING,
+)
diff --git a/source/gui/metrics/bgp_peer.py b/source/gui/metrics/bgp_peer.py
deleted file mode 100644
index a6f8bcfef666f906a9c6f46baaf268ff56e8ac59..0000000000000000000000000000000000000000
--- a/source/gui/metrics/bgp_peer.py
+++ /dev/null
@@ -1,253 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#
-# License: GNU General Public License v2
-#
-# Author: thl-cmk[at]outlook[dot]com
-# URL   : https://thl-cmk.hopto.org
-# Date  : 2021-08-21
-#
-# BGP Peer metrics plugin
-#
-# 2024-05-17: fixed typo in range mac -> max (crashed graphs in cmk 2.3.)
-#
-from cmk.gui.i18n import _
-
-from cmk.gui.plugins.metrics.utils import (
-    metric_info,
-    graph_info,
-    perfometer_info
-)
-
-#####################################################################################################################
-#
-# define metrics for bgp peer perfdata
-#
-#####################################################################################################################
-
-metric_info['bgp_peer_inupdates'] = {
-    'title': _('Updates received'),
-    'unit': '1/s',
-    'color': '22/a',
-}
-metric_info['bgp_peer_outupdates'] = {
-    'title': _('Updates send'),
-    'unit': '1/s',
-    'color': '32/a',
-}
-metric_info['bgp_peer_intotalmessages'] = {
-    'title': _('Messages received'),
-    'unit': '1/s',
-    'color': '42/a',
-}
-metric_info['bgp_peer_outtotalmessages'] = {
-    'title': _('Messages send'),
-    'unit': '1/s',
-    'color': '13/a',
-}
-metric_info['bgp_peer_fsmestablishedtransitions'] = {
-    'title': _('FSM transitions'),
-    'unit': 'count',
-    'color': '23/a',
-}
-metric_info['bgp_peer_fsmestablishedtime'] = {
-    'title': _('FSM last change'),
-    'unit': 's',
-    'color': '26/a',
-}
-metric_info['bgp_peer_inupdateelapsedtime'] = {
-    'title': _('Last update received'),
-    'unit': 's',
-    'color': '43/a',
-}
-
-metric_info['bgp_peer_acceptedprefixes'] = {
-    'title': _('Prefixes accepted'),
-    'help': _('number of accepted prefixes'),
-    'unit': 'count',
-    'color': '11/a',
-}
-metric_info['bgp_peer_deniedprefixes'] = {
-    'title': _('Prefixes denied'),
-    'unit': '1/s',
-    'color': '21/a',
-}
-metric_info['bgp_peer_advertisedprefixes'] = {
-    'title': _('Prefixes advertised'),
-    'unit': '1/s',
-    'color': '31/a',
-}
-metric_info['bgp_peer_withdrawnprefixes'] = {
-    'title': _('Prefixes withdrawn'),
-    'unit': '1/s',
-    'color': '41/a',
-}
-metric_info['bgp_peer_suppressedprefixes'] = {
-    'title': _('Prefixes suppressed'),
-    'unit': '1/s',
-    'color': '12/a',
-}
-
-# Juniper specific metrics
-metric_info['bgp_peer_in_prefixes'] = {
-    'title': _('Prefixes in'),
-    'unit': 'count',
-    'color': '11/a',
-}
-metric_info['bgp_peer_in_prefixes_rejected'] = {
-    'title': _('Prefixes in rejected'),
-    'unit': 'count',
-    'color': '21/a',
-}
-metric_info['bgp_peer_in_prefixes_active'] = {
-    'title': _('Prefixes in active'),
-    'unit': 'count',
-    'color': '31/a',
-}
-metric_info['bgp_peer_out_prefixes'] = {
-    'title': _('Prefixes out'),
-    'unit': 'count',
-    'color': '41/a',
-}
-
-# Hawei spezific metrics
-metric_info['bgp_peer_prefixrcvcounter'] = {
-    'title': _('Prefixes received'),
-    'unit': 'count',
-    'color': '11/a',
-}
-metric_info['bgp_peer_prefixactivecounter'] = {
-    'title': _('Prefixes active'),
-    'unit': 'count',
-    'color': '33/a',
-}
-metric_info['bgp_peer_prefixadvcounter'] = {
-    'title': _('Prefixes advertised'),
-    'unit': 'count',
-    'color': '43/a',
-}
-
-######################################################################################################################
-#
-# how to graph perdata for bgp peer
-#
-######################################################################################################################
-
-graph_info['bgp_peer.fms_transitions_last_change'] = {
-    'title': _('FSM established last change'),
-    'metrics': [
-        ('bgp_peer_fsmestablishedtime', 'area'),
-    ],
-    'range': (0, 'bgp_peer_fsmestablishedtime:max'),
-}
-
-graph_info['bgp_peer.prefixes_accepted'] = {
-    'title': _('Accepted Prefixes'),
-    'metrics': [
-        ('bgp_peer_acceptedprefixes', 'area'),
-    ],
-    'scalars': [
-        ('bgp_peer_acceptedprefixes:crit', _('crit')),
-        ('bgp_peer_acceptedprefixes:warn', _('warn')),
-    ],
-    'range': (0, 'bgp_peer_acceptedprefixes:max'),
-}
-
-graph_info['bgp_peer.prefixes_per_second'] = {
-    'title': _('Prefixes/s'),
-    'metrics': [
-        ('bgp_peer_withdrawnprefixes', 'line'),
-        ('bgp_peer_suppressedprefixes', 'line'),
-        ('bgp_peer_deniedprefixes', 'line'),
-        ('bgp_peer_advertisedprefixes', 'line'),
-    ],
-    'optional_metrics': [
-        'bgp_peer_withdrawnprefixes',
-        'bgp_peer_suppressedprefixes',
-        'bgp_peer_deniedprefixes',
-        'bgp_peer_advertisedprefixes',
-    ],
-}
-
-graph_info['bgp_peer.updates_in_out'] = {
-    'title': _('Updates'),
-    'metrics': [
-        ('bgp_peer_outupdates', '-area'),
-        ('bgp_peer_inupdates', 'area'),
-    ]
-}
-
-graph_info['bgp_peer.messages_in_out'] = {
-    'title': _('Total messages'),
-    'metrics': [
-        ('bgp_peer_outtotalmessages', '-area'),
-        ('bgp_peer_intotalmessages', 'area'),
-    ]
-}
-
-graph_info['bgp_peer.fms_transitions_from_to'] = {
-    'title': _('FSM transitions from/to established'),
-    'metrics': [
-        ('bgp_peer_fsmestablishedtransitions', 'area'),
-    ],
-    'range': (0, 'bgp_peer_fsmestablishedtransitions:max'),
-}
-
-
-graph_info['bgp_peer.time_since_last_update'] = {
-    'title': _('Time since last update received'),
-    'metrics': [
-        ('bgp_peer_inupdateelapsedtime', 'area'),
-    ],
-    'range': (0, 'bgp_peer_inupdateelapsedtime:max'),
-}
-
-# juniper prefixes
-graph_info['bgp_peer.juniper_prefixes'] = {
-    'title': _('Prefixes in/out'),
-    'metrics': [
-        ('bgp_peer_out_prefixes', '-line'),
-        ('bgp_peer_in_prefixes_rejected', 'line'),
-        ('bgp_peer_in_prefixes_active', 'line'),
-        ('bgp_peer_in_prefixes', 'line'),
-    ],
-}
-
-# huawei prefixes
-graph_info['huawei_bgp_peer_counter'] = {
-    'title': _('BGP prefix counter'),
-    'metrics': [
-        ('bgp_peer_prefixrcvcounter', 'line'),
-        ('bgp_peer_prefixactivecounter', 'line'),
-        ('bgp_peer_prefixadvcounter', 'line'),
-    ]
-}
-
-
-######################################################################################################################
-#
-# define perf-o-meter for bgp peer uptime + prefixes accepted/advertised
-#
-######################################################################################################################
-
-perfometer_info.append(('stacked', [
-    {
-        'type': 'logarithmic',
-        'metric': 'bgp_peer_fsmestablishedtime',
-        'half_value': 2592000.0,  # ome month
-        'exponent': 2,
-    },
-    {
-        'type': 'logarithmic',
-        'metric': 'bgp_peer_acceptedprefixes',
-        'half_value': 500000.0,
-        'exponent': 2,
-    }
-]))
-
-perfometer_info.append({
-    'type': 'logarithmic',
-    'metric': 'bgp_peer_fsmestablishedtime',
-    'half_value': 2592000.0,  # ome month
-    'exponent': 2,
-})
diff --git a/source/gui/wato/check_parameters/bgp_peer.py b/source/gui/wato/check_parameters/bgp_peer.py
index 32744b040396afac45d450de452d3e772de620ec..812659dc4832a139f4c8e1c183d87ba75a91af73 100644
--- a/source/gui/wato/check_parameters/bgp_peer.py
+++ b/source/gui/wato/check_parameters/bgp_peer.py
@@ -1,256 +1,4 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
-#
-# License: GNU General Public License v2
-#
-# Author: thl-cmk[at]outlook[dot]com
-# URL   : https://thl-cmk.hopto.org
-# Date  : 2017-12-25
-#
-# Check_MK bgp_peers WATO plugin
-#
-# 2021-03-27: rewrite for CMK 2.0
-# 2021-08-21: modified for bgp_peer plugin (from cisco_bgp_peer)
-# 2021-08-29: removed htmloutput and infotext_values option
-# 2022-04-02: added bgp neighbour states
-# 2022-04-29: added upper/lower prefix limit
-# 2022-05-09: added discovery rule set
-# 2022-05-11: added remote_as to build_item
-# 2022-09-05: added internal_item to avoid warnings on cmk updates (THX to Jay2k1 for reporting the issue)
-# 2023-06-11: moved wato file from ~local/lib/check_mk/gui/plugins/wato
-#             to ~/local/lib/check_mk/gui/plugins/wato/check_parameters to override the build in wato plugin
-# 2024-06-15: fixed typo (no not)
-#
-# Known issue: the override of the build in wato plugin will break the build in aritsa bgp peer plugin
 
-from cmk.gui.i18n import _
-from cmk.gui.valuespec import (
-    Dictionary,
-    Integer,
-    TextAscii,
-    ListOf,
-    Tuple,
-    TextUnicode,
-    MonitoringState,
-    ListChoice,
-    TextInput,
-)
-
-from cmk.gui.plugins.wato.utils import (
-    CheckParameterRulespecWithItem,
-    rulespec_registry,
-    RulespecGroupCheckParametersNetworking,
-    HostRulespec,
-    RulespecGroupCheckParametersDiscovery,
-)
-
-
-def _parameter_valuespec_bgp_peer():
-    return Dictionary(
-        elements=[
-            ('minuptime',
-             Tuple(
-                 title=_('Minimum uptime for peer'),
-                 orientation='horizontal',
-                 help=_('Set the time in seconds, a peer must be up before the peer is considered sable.'),
-                 elements=[
-                     Integer(title=_('Warning below'), unit='seconds', default_value=7200, minvalue=0),
-                     Integer(title=_('Critical below'), unit='seconds', default_value=3600, minvalue=0)
-                 ],
-             )),
-            ('accepted_prefixes_upper_levels',
-             Tuple(
-                 title=_('Accepted prefixes upper levels'),
-                 help=_('The values from WATO are preferred to the values from the device.'),
-                 orientation='horizontal',
-                 elements=[
-                     Integer(title=_('Warning at'), minvalue=0, unit=_('prefixes'), size=5),
-                     Integer(title=_('Critical at'), minvalue=0, unit=_('prefixes'), size=5),
-                 ],
-             )),
-            ('accepted_prefixes_lower_levels',
-             Tuple(
-                 title=_('Accepted prefixes lower levels'),
-                 orientation='horizontal',
-                 elements=[
-                     Integer(title=_('Warning below'), minvalue=0, unit=_('prefixes'), size=5),
-                     Integer(title=_('Critical below'), minvalue=0, unit=_('prefixes'), size=5),
-                 ],
-             )),
-            ('peernotfound',
-             MonitoringState(
-                 default_value=2,
-                 title=_('State if peer is not found.'),
-                 help=_('Default monitoring state if the peer is not found in the SNMP data')
-             )),
-            ('admindown',
-             MonitoringState(
-                 default_value=1,
-                 title=_('State if peer is admin shutdown.'),
-                 help=_('Monitoring state if the peer is admin shutdown')
-             )),
-            ('neighborstate',
-             Dictionary(
-                 title=_('State to report for BGP neighbor state'),
-                 help=_('Map each BGP state to a CheckMK monitoring state'),
-                 elements=[
-                     ('1',
-                      MonitoringState(
-                          title=_('1 - idle'),
-                          help=_(
-                              'This is the first stage of the BGP FSM. BGP detects a start event, tries to initiate a '
-                              'TCP connection to the BGP peer, and also listens for a new connect from a peer router. '
-                              'If an error causes BGP to go back to the Idle state for a second time, the '
-                              'ConnectRetryTimer is set to 60 seconds and must decrement to zero before the connection '
-                              'is initiated again. Further failures to leave the Idle state result in the '
-                              'ConnectRetryTimer doubling in length from the previous time. '
-                              'Default monitoring state is "CRIT"'),
-                          default_value=2,
-                      )),
-                     ('2',
-                      MonitoringState(
-                          title=_('2 - connect'),
-                          help=_(
-                              'In this state, BGP initiates the TCP connection. If the 3-way TCP handshake completes, '
-                              'the established BGP Session BGP process resets the ConnectRetryTimer and sends the Open '
-                              'message to the neighbor, and then changes to the OpenSent State.'
-                              'Default monitoring state is "WARN"'),
-                          default_value=1,
-                      )),
-                     ('3',
-                      MonitoringState(
-                          title=_('3 - active'),
-                          help=_('In this state, BGP starts a new 3-way TCP handshake. If a connection is established, '
-                                 'an Open message is sent, the Hold Timer is set to 4 minutes, and the state moves to '
-                                 'OpenSent. If this attempt for TCP connection fails, the state moves back to the Connect '
-                                 'state and resets the ConnectRetryTimer. '
-                                 'Default monitoring state is "WARN"'),
-                          default_value=1,
-                      )),
-                     ('4',
-                      MonitoringState(
-                          title=_('4 - opensent'),
-                          help=_(
-                              'In this state, an Open message has been sent from the originating router and is awaiting '
-                              'an Open message from the other router. After the originating router receives the OPEN '
-                              'message from the other router, both OPEN messages are checked for errors. If the Open '
-                              'messages do not have any errors, the Hold Time is negotiated (using the lower value), '
-                              'and a KEEPALIVE message is sent (assuming the value is not set to zero). The connection '
-                              'state is then moved to OpenConfirm. If an error is found in the OPEN message, a '
-                              'Notification message is sent, and the state is moved back to Idle.'
-                              ' Default monitoring state is "WARN"'),
-                          default_value=1,
-                      )),
-                     ('5',
-                      MonitoringState(
-                          title=_('5 - openconfirm'),
-                          help=_('In this state, BGP waits for a Keepalive or Notification message. Upon receipt of a '
-                                 'neighbor’s Keepalive, the state is moved to Established. If the hold timer expires, a '
-                                 'stop event occurs, or a Notification message is received, and the state is moved to '
-                                 'Idle. '
-                                 'Default monitoring state is "WARN"'),
-                          default_value=1,
-                      )),
-                     ('6',
-                      MonitoringState(
-                          title=_('6 - established'),
-                          help=_(
-                              'In this state, the BGP session is established. BGP neighbors exchange routes via Update '
-                              'messages. As Update and Keepalive messages are received, the Hold Timer is reset. If the '
-                              'Hold Timer expires, an error is detected and BGP moves the neighbor back to the Idle '
-                              'state. '
-                              'Default monitoring state is "OK"'),
-                          default_value=0,
-                      )),
-                 ])),
-            ('noprefixlimit',
-             MonitoringState(
-                 default_value=1,
-                 title=_('State if no admin prefix limit/warn threshold is configured.'),
-                 help=_('The admin prefix limit and warn threshold needs to be configured on the device. '
-                        'For example: "neighbor 172.17.10.10 maximum-prefix 10000 80". The threshold is in percentage '
-                        'of the prefix limit.')
-             )),
-            ('peer_list',
-             ListOf(
-                 Tuple(
-                     orientation='horizontal',
-                     elements=[
-                         TextUnicode(
-                             title=_('BGP Peer'),
-                             help=_('The configured value must match a BGP item reported by the monitored '
-                                    'device. For example: "10.194.115.98" or "2A10:1CD0:1020:135::20 IPv6 Unicast"'),
-                             allow_empty=False,
-                             size=50,
-                         ),
-                         TextUnicode(
-                             title=_('BGP Peer Alias'),
-                             help=_('You can configure an individual alias here for the BGP peer matching '
-                                    'the text configured in the "BGP Peer IP-address" field. The alias will '
-                                    'be shown in the check info'),
-                             size=50,
-                         ),
-                         MonitoringState(
-                             default_value=2,
-                             title=_('State if not found'),
-                             help=_('You can configure an individual state if the BGP peer matching the text '
-                                    'configured in the "BGP Peer IP-address" field is not found')
-                         ),
-                     ]),
-                 add_label=_('Add BGP peer'),
-                 movable=False,
-                 title=_('BGP Peers'),
-             )),
-            ('internal_item',  # added by plugin discovery function
-             TextUnicode()),
-        ],
-        hidden_keys=['internal_item'],
-    )
-
-
-rulespec_registry.register(
-    CheckParameterRulespecWithItem(
-        check_group_name='bgp_peer',
-        group=RulespecGroupCheckParametersNetworking,
-        item_spec=lambda: TextInput(title=_('BGP peer'), ),
-        match_type='dict',
-        parameter_valuespec=_parameter_valuespec_bgp_peer,
-        title=lambda: _('BGP peer'),
-    ))
-
-
-def _valuespec_discovery_bgp_peer():
-    item_parts = [
-        # ('remote_address', 'Peer remote address'),
-        ('remote_as', 'Remote AS'),
-        ('address_family', 'Address family'),
-        ('routing_instance', 'Routing instance/VRF'),
-
-    ]
-    return Dictionary(
-            title=_('BGP peer'),
-            elements=[
-                ('build_item',
-                 ListChoice(
-                     title=_('Information not to use in the item name'),
-                     help=_(
-                         'The Peer remote address is always used as the item name. By default the check will add the '
-                         'address-family and the routing instance/VRF if available. You can decide to not use these '
-                         'additional information in the item name. Do so only if your peers have only one address-'
-                         'family configured and you don\'t have the same peer remote address in different routing '
-                         'instances/VRFs configured.'
-                     ),
-                     choices=item_parts,
-                     default_value=['remote_as'],
-                 )),
-            ],
-        )
-
-
-rulespec_registry.register(
-    HostRulespec(
-        group=RulespecGroupCheckParametersDiscovery,
-        match_type='dict',
-        name='discovery_bgp_peer',
-        valuespec=_valuespec_discovery_bgp_peer,
-    ))
+# dummy to shadow build in bgp peer rule set
\ No newline at end of file
diff --git a/source/gui/wato/check_parameters/inv_bgp_peer.py b/source/gui/wato/check_parameters/inv_bgp_peer.py
deleted file mode 100644
index e728182a2fddb8aa1d85e7c0a63a8e99bc2dfafa..0000000000000000000000000000000000000000
--- a/source/gui/wato/check_parameters/inv_bgp_peer.py
+++ /dev/null
@@ -1,109 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#
-# Author: thl-cmk[at]outlook[dot]com
-# URL   : https://thl-cmk.hopto.org
-# Date  : 2022-04-24
-#
-# 2022-04-24: added option for BGP down time
-#             added option to remove some columns from inventory
-# 2022-04-28: added Whois options
-# 2023-06-12: moved wato file from ~local/lib/check_mk/gui/plugins/wato
-#             to ~/local/lib/check_mk/gui/plugins/wato/check_parameters
-
-from cmk.gui.i18n import _
-from cmk.gui.plugins.wato.utils import (
-    HostRulespec,
-    rulespec_registry,
-)
-from cmk.gui.valuespec import (
-    Dictionary,
-    ListChoice,
-    Age,
-    DropdownChoice,
-    Integer,
-)
-
-from cmk.gui.plugins.wato.inventory import (
-    RulespecGroupInventory,
-)
-
-
-def _valuespec_inv_bgp_peer():
-    removecolumns = [
-        # ('remote_as', 'Remote AS'),
-        # ('remote_id', 'Remote ID'),
-        # ('local_addr', 'Local address'),
-        # ('local_as', 'Local AS'),
-        # ('local_id', 'Local ID'),
-        ('address_family', 'Address family'),
-        ('last_error', 'Last error'),
-        ('last_error_code', 'Last error code'),
-        # ('prev_state', 'Previous state'),
-        ('as_name', 'Remote AS name'),
-        ('as_org_name', 'Remote AS Org Name'),
-        ('bgp_type', 'Type'),
-        ('version', 'Version'),
-    ]
-
-    return Dictionary(
-        title=_('BGP peer'),
-        elements=[
-            ('not_in_service_time',
-             Age(
-                 title=_('Time peer is not up until considered not in service'),
-                 default_value=2592000,  # 30 days in seconds,
-             )),
-            ('remove_columns',
-             ListChoice(
-                 title=_('List of columns to remove'),
-                 help=_('Information to remove from inventory'),
-                 choices=removecolumns,
-                 default_value=[],
-             )),
-            ('whois_enable',
-             Dictionary(
-                 title=_('Add whois data to the inventory'),
-                 help=_(
-                     'The whois data will be fetched via RDAP from the registries. For this the the plugin tries to'
-                     'find the best registry via the RDAP bootstrap data from https://data.iana.org/rdap/asn.json.'
-                     'The query it self will go to the found registry via http(s). Note: the request might be get '
-                     'redirected if there a different authoritative registry for the ASn'
-                 ),
-                 elements=[
-                     ('whois_rir',
-                      DropdownChoice(
-                          title='Preferred RIR to fetch whois data',
-                          help=_(
-                              'This registry will be used if the plugin can not determine the authoritative registry '
-                              'based on the bootstrap data.'
-                          ),
-                          choices=[
-                              ('afrinic', _('AFRINIC (https://rdap.afrinic.net/rdap)')),
-                              ('apnic', _('APNIC (https://rdap.apnic.net)')),
-                              ('arin', _('ARIN (https://rdap.arin.net/registry)')),
-                              ('ripe', _('RIPE (https://rdap.db.ripe.net)')),
-                              ('lacnic', _('LACNIC (https://rdap.apnic.net)')),
-                          ]
-                      )),
-                     ('whois_timeout',
-                      Integer(
-                          title='Timeout for connections to RIRs',
-                          help=_('The connection timeout for each whois request.'),
-                          default_value=5,
-                          minvalue=1,
-                          unit=_('seconds'),
-                      )),
-                 ]
-             )),
-        ],
-    )
-
-
-rulespec_registry.register(
-    HostRulespec(
-        group=RulespecGroupInventory,
-        match_type='dict',
-        name='inv_parameters:inv_bgp_peer',
-        valuespec=_valuespec_inv_bgp_peer,
-    ))
diff --git a/source/packages/bgp_peer b/source/packages/bgp_peer
index 0bc57580780d775fbcbabcaa0150927d0a8405cc..a8979990117c3c59074a41280efcf6eb2ca52e14 100644
--- a/source/packages/bgp_peer
+++ b/source/packages/bgp_peer
@@ -27,17 +27,19 @@
                 'https://thl-cmk.hopto.org/gitlab/checkmk/juniper-networks/juniper_bgp_peer\n'
                 '\n',
  'download_url': 'https://thl-cmk.hopto.org/vendor-independent/bgp_peer',
- 'files': {'agent_based': ['bgp_peer.py',
-                           'inv_bgp_peer.py',
-                           'utils/bgp_peer.py'],
-           'checkman': ['bgp_peer'],
-           'gui': ['metrics/bgp_peer.py',
-                   'wato/check_parameters/bgp_peer.py',
-                   'wato/check_parameters/inv_bgp_peer.py'],
+ 'files': {'agent_based': ['bgp_peer.py'],
+           'cmk_addons_plugins': ['bgp_peer/agent_based/bgp_peer.py',
+                                  'bgp_peer/agent_based/inv_bgp_peer.py',
+                                  'bgp_peer/graphing/bgp_peer.py',
+                                  'bgp_peer/lib/bgp_peer.py',
+                                  'bgp_peer/rulesets/bgp_peer.py',
+                                  'bgp_peer/rulesets/inv_bgp_peer.py',
+                                  'bgp_peer/checkman/bgp_peer'],
+           'gui': ['wato/check_parameters/bgp_peer.py'],
            'web': ['plugins/views/inv_bgp_peer.py']},
  'name': 'bgp_peer',
  'title': 'BGP Peer',
- 'version': '2.2.8-20250117',
+ 'version': '2.3.0-20250329',
  'version.min_required': '2.3.0b1',
  'version.packaged': 'cmk-mkp-tool 0.2.0',
- 'version.usable_until': '2.4.0b1'}
+ 'version.usable_until': '2.5.0b1'}