protocol_limits
Retrieve regulatory emission limits for short-range wireless protocols such as Zigbee, LoRa, UWB, and more, based on your region (FCC, ETSI, etc.).
Instructions
Get regulatory limits for short-range wireless protocols: Zigbee/Thread/Matter, LoRa/LoRaWAN, UWB, Wi-SUN, DECT, NB-IoT, LTE-M.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| protocol | Yes | Wireless protocol | |
| region | No | Regulatory region |
Implementation Reference
- The _protocol_limits() static method is the actual handler that implements the 'protocol_limits' tool logic. It takes a 'protocol' (zigbee, lora, uwb, wisun, dect, nb_iot, lte_m) and optional 'region' argument, looks up data from loaded JSON (short_range_wireless.json), and returns formatted regulatory limits including frequency bands, power limits, duty cycles, and region-specific rules.
@staticmethod def _protocol_limits(arguments: dict[str, Any]) -> list[TextContent]: protocol = arguments["protocol"] region_filter = arguments.get("region", "all") protocols = SHORT_RANGE.get("protocols", {}) proto_data = protocols.get(protocol) if not proto_data: return [TextContent(type="text", text=f"Unknown protocol: {protocol}. Available: {', '.join(protocols.keys())}")] result = f"{proto_data['label']} Regulatory Limits\n{'=' * 55}\n\n" result += f"Standard: {proto_data.get('standard', 'N/A')}\n" if proto_data.get("modulation"): result += f"Modulation: {proto_data['modulation']}\n" if proto_data.get("typical_range_km"): rng = proto_data["typical_range_km"] result += f"Typical range: {rng.get('urban', '?')} km (urban), {rng.get('rural', '?')} km (rural)\n" if proto_data.get("typical_range_m"): rng = proto_data["typical_range_m"] result += f"Typical range: {rng.get('indoor', '?')}m (indoor), {rng.get('outdoor', '?')}m (outdoor)\n" if proto_data.get("data_rate_kbps"): dr = proto_data["data_rate_kbps"] result += f"Data rate: {dr.get('min', '?')}-{dr.get('max', '?')} kbps\n" if proto_data.get("data_rate_mbps"): dr = proto_data["data_rate_mbps"] result += f"Data rate: {dr.get('min', '?')}-{dr.get('max', '?')} Mbps\n" if proto_data.get("bandwidth_khz"): result += f"Bandwidth: {proto_data['bandwidth_khz']} kHz\n" if proto_data.get("bandwidth_mhz"): result += f"Bandwidth: {proto_data['bandwidth_mhz']} MHz\n" if proto_data.get("also_used_by"): result += f"Also used by: {', '.join(proto_data['also_used_by'])}\n" # UE power classes (for NB-IoT / LTE-M) if proto_data.get("max_ue_power_classes"): result += "\n## UE Power Classes:\n" for pc in proto_data["max_ue_power_classes"]: result += f" Class {pc['class']}: {pc['max_power_dbm']} dBm" if pc.get("notes"): result += f" - {pc['notes']}" result += "\n" if proto_data.get("deployment_modes"): result += f"\nDeployment modes: {', '.join(proto_data['deployment_modes'])}\n" # Bands with regional limits bands = proto_data.get("bands", {}) if bands: result += "\n## Frequency Bands & Limits:\n" for band_key, band_data in bands.items(): freq = band_data.get("frequency_mhz", [0, 0]) label = band_data.get("label", band_key) result += f"\n ### {label} ({freq[0]}-{freq[1]} MHz)\n" if band_data.get("channels"): result += f" Channels: {band_data['channels']}\n" if band_data.get("uplink_channels"): result += f" Uplink channels: {band_data['uplink_channels']}\n" if band_data.get("downlink_channels"): result += f" Downlink channels: {band_data['downlink_channels']}\n" # Sub-bands (e.g., LoRa EU duty cycle bands) sub_bands = band_data.get("sub_bands", []) if sub_bands: result += " Sub-bands:\n" for sb in sub_bands: result += f" {sb['freq_mhz'][0]}-{sb['freq_mhz'][1]} MHz: {sb['max_eirp_dbm']} dBm, {sb['duty_cycle_percent']}% duty cycle" if sb.get("notes"): result += f" ({sb['notes']})" result += "\n" # Common channels (UWB) common_ch = band_data.get("common_channels", {}) if common_ch: result += " Common channels:\n" for ch_name, ch_data in common_ch.items(): result += f" {ch_name}: {ch_data['center_mhz']} MHz center, {ch_data['bandwidth_mhz']} MHz BW" if ch_data.get("notes"): result += f" ({ch_data['notes']})" result += "\n" regions = band_data.get("regions", {}) for region_key, region_data in regions.items(): if region_filter != "all" and region_key != region_filter: continue if isinstance(region_data, bool): continue result += f"\n [{region_key.upper()}]\n" if region_data.get("regulatory_section"): result += f" Section: {region_data['regulatory_section']}\n" if region_data.get("regulatory_standard"): result += f" Standard: {region_data['regulatory_standard']}\n" if region_data.get("max_eirp_dbm") is not None: result += f" Max EIRP: {region_data['max_eirp_dbm']} dBm\n" if region_data.get("max_conducted_power_dbm") is not None: result += f" Max conducted: {region_data['max_conducted_power_dbm']} dBm\n" if region_data.get("max_eirp_density_dbm_mhz") is not None: result += f" Max EIRP density: {region_data['max_eirp_density_dbm_mhz']} dBm/MHz\n" if region_data.get("duty_cycle_percent") is not None: result += f" Duty cycle: {region_data['duty_cycle_percent']}%\n" if region_data.get("notes"): result += f" Notes: {region_data['notes']}\n" # Standalone regions (UWB) if proto_data.get("regions"): result += "\n## Regional Limits:\n" for region_key, region_data in proto_data["regions"].items(): if region_filter != "all" and region_key != region_filter: continue result += f"\n [{region_key.upper()}]\n" if region_data.get("regulatory_section"): result += f" Section: {region_data['regulatory_section']}\n" if region_data.get("regulatory_standard"): result += f" Standard: {region_data['regulatory_standard']}\n" if region_data.get("max_eirp_density_dbm_mhz") is not None: result += f" Max EIRP density: {region_data['max_eirp_density_dbm_mhz']} dBm/MHz\n" if region_data.get("max_peak_eirp_dbm") is not None: result += f" Max peak EIRP: {region_data['max_peak_eirp_dbm']} dBm\n" if region_data.get("frequency_mhz"): result += f" Frequency: {region_data['frequency_mhz'][0]}-{region_data['frequency_mhz'][1]} MHz\n" if region_data.get("notes"): result += f" Notes: {region_data['notes']}\n" if proto_data.get("notes"): result += f"\n{proto_data['notes']}\n" return [TextContent(type="text", text=result)] - The inputSchema for the 'protocol_limits' tool is defined in the list_tools() method. It declares 'protocol' (required, enum: zigbee, lora, uwb, wisun, dect, nb_iot, lte_m) and 'region' (optional, enum: fcc, etsi, ised, japan, korea, all).
Tool( name="protocol_limits", description=( "Get regulatory limits for short-range wireless protocols: " "Zigbee/Thread/Matter, LoRa/LoRaWAN, UWB, Wi-SUN, DECT, NB-IoT, LTE-M." ), inputSchema={ "type": "object", "properties": { "protocol": { "type": "string", "enum": ["zigbee", "lora", "uwb", "wisun", "dect", "nb_iot", "lte_m"], "description": "Wireless protocol", }, "region": { "type": "string", "enum": ["fcc", "etsi", "ised", "japan", "korea", "all"], "description": "Regulatory region", }, }, "required": ["protocol"], }, ), - src/mcp_emc_regulations/tools/wireless.py:111-117 (registration)The call_tool() dispatcher in the WirelessTools class routes incoming tool calls to the _protocol_limits() method when the tool name is 'protocol_limits'.
async def call_tool(self, name: str, arguments: dict[str, Any]) -> list[TextContent]: if name == "wifi_limits": return self._wifi_limits(arguments) elif name == "ble_limits": return self._ble_limits(arguments) elif name == "protocol_limits": return self._protocol_limits(arguments) - src/mcp_emc_regulations/registry.py:41-53 (registration)The ToolRegistry auto-discovers all ToolModule subclasses (including WirelessTools) via pkgutil, which enables the 'protocol_limits' tool to be registered and found dynamically.
def _discover(self) -> None: """Import every module in the ``tools`` package and instantiate ToolModules.""" for info in pkgutil.iter_modules(_tools_pkg.__path__, _tools_pkg.__name__ + "."): module = importlib.import_module(info.name) for attr_name in dir(module): attr = getattr(module, attr_name) if ( isinstance(attr, type) and issubclass(attr, ToolModule) and attr is not ToolModule ): self._modules.append(attr()) - The load_json() utility loads the 'short_range_wireless.json' data file used by the _protocol_limits() handler to retrieve protocol-specific regulatory limits.
def load_json(filename: str) -> dict: """Load a JSON data file from the package data directory.""" filepath = DATA_DIR / filename if filepath.exists(): return json.loads(filepath.read_text()) return {}