cfg_parse
Parse an OpenSIPS configuration to extract its structure, including modules, parameters, listen addresses, routes, and global settings.
Instructions
Parse an OpenSIPS configuration and extract its structure.
Returns modules, modparams, listen addresses, routes, and global params.
Parameters
config_content: The full OpenSIPS configuration text to parse.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| config_content | Yes |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- The cfg_parse tool handler function. Decorated with @mcp.tool() and @require_permission('config.read'). Takes config_content string, calls _parser.parse(), and returns parsed model as dict.
@mcp.tool() @require_permission("config.read") async def cfg_parse( ctx: Context, config_content: str, ) -> dict[str, Any]: """Parse an OpenSIPS configuration and extract its structure. Returns modules, modparams, listen addresses, routes, and global params. Parameters ---------- config_content: The full OpenSIPS configuration text to parse. """ try: parsed = _parser.parse(config_content) return parsed.model_dump() except Exception as exc: return {"error": str(exc)} - src/opensips_mcp/tools/cfg_tools.py:175-194 (registration)The @mcp.tool() decorator registers the cfg_parse function as an MCP tool named 'cfg_parse'.
@mcp.tool() @require_permission("config.read") async def cfg_parse( ctx: Context, config_content: str, ) -> dict[str, Any]: """Parse an OpenSIPS configuration and extract its structure. Returns modules, modparams, listen addresses, routes, and global params. Parameters ---------- config_content: The full OpenSIPS configuration text to parse. """ try: parsed = _parser.parse(config_content) return parsed.model_dump() except Exception as exc: return {"error": str(exc)} - The ConfigParser class which provides the parse() method that cfg_parse delegates to. Extracts modules, modparams, listen addresses, routes, and global params from raw OpenSIPS config text.
class ConfigParser: """Extracts structure from raw OpenSIPS configuration text.""" _LOADMODULE_RE = re.compile(r'loadmodule\s+"?([^"\s]+)"?') _MODPARAM_RE = re.compile( r'modparam\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*(.+?)\s*\)' ) _LISTEN_RE = re.compile(r"listen\s*=\s*(.+)") _ROUTE_RE = re.compile( r"((?:route|branch_route|onreply_route|failure_route|local_route|error_route)" r"\s*(?:\[[\w]+\])?\s*)\{" ) _GLOBAL_RE = re.compile(r"^(\w+)\s*=\s*(.+)$", re.MULTILINE) def parse(self, config_content: str) -> ParsedConfig: """Parse *config_content* and return structured data. Two-pass parse: directives that need the ORIGINAL string contents (``loadmodule "tm.so"`` etc.) read from the raw text; route bodies and brace-counted regions read from the ``_scrub_comments_and_strings`` version so commented-out code and string-literal braces cannot confuse the linter. """ result = ParsedConfig() scrubbed = _scrub_comments_and_strings(config_content) # Modules — read from scrubbed so commented-out loadmodule lines do # not register as loaded. The first quoted arg of loadmodule # survived as quote-pair-with-spaces inside; we therefore scan the # ORIGINAL text but only at offsets where the scrubbed text still # has the directive keyword present (i.e. the directive itself is # not inside a comment). for m in self._LOADMODULE_RE.finditer(config_content): if "loadmodule" in scrubbed[m.start():m.end()]: result.modules.append(m.group(1)) # Module parameters — same pattern: keyword presence in scrubbed # text means the directive is live, then read full args from original. for m in self._MODPARAM_RE.finditer(config_content): if "modparam" in scrubbed[m.start():m.end()]: result.modparams.append( { "module": m.group(1), "param": m.group(2), "value": m.group(3).strip().rstrip(")").strip('"'), } ) # Listen addresses for m in self._LISTEN_RE.finditer(config_content): if "listen" in scrubbed[m.start():m.end()]: result.listen_addresses.append(m.group(1)) # Route blocks — extracted from SCRUBBED text so a literal `{` or # `}` inside a string does not desync the brace counter, and the # captured body has comments/strings nulled so substring linter # rules see only live code. for m in self._ROUTE_RE.finditer(scrubbed): route_name = m.group(1).strip() start = m.end() depth = 1 pos = start while pos < len(scrubbed) and depth > 0: if scrubbed[pos] == "{": depth += 1 elif scrubbed[pos] == "}": depth -= 1 pos += 1 result.routes[route_name] = scrubbed[start : pos - 1].strip() # Global parameters for m in self._GLOBAL_RE.finditer(scrubbed): key = m.group(1) if not key.startswith(("route", "loadmodule", "modparam", "#")): result.global_params[key] = m.group(2).strip() return result - src/opensips_mcp/cfg/parser.py:10-17 (schema)The ParsedConfig Pydantic model defining the output schema for cfg_parse results: modules (list), modparams (list), listen_addresses (list), routes (dict), global_params (dict).
class ParsedConfig(BaseModel): """Structured representation of a parsed OpenSIPS configuration.""" modules: list[str] = [] modparams: list[dict[str, str]] = [] listen_addresses: list[str] = [] routes: dict[str, str] = {} global_params: dict[str, str] = {} - The function signature acts as the input schema via FastMCP/Context: accepts a 'config_content' string parameter.
@mcp.tool() @require_permission("config.read") async def cfg_parse( ctx: Context, config_content: str, ) -> dict[str, Any]: """Parse an OpenSIPS configuration and extract its structure. Returns modules, modparams, listen addresses, routes, and global params. Parameters ---------- config_content: The full OpenSIPS configuration text to parse. """