Skip to main content
Glama

unmap_stack_trace

Reverse obfuscated identifiers in a Python stack trace using a mapping.json file.

Instructions

Reverse obfuscated identifiers in a stack trace using a pyobfus mapping.json. Accepts the trace as plain text and the path to a mapping file produced by --save-mapping.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
traceYes
mapping_pathYes

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • Main tool handler: unmap_stack_trace loads an ObfuscationMapping from a JSON file, calls mapping.unmap_text(trace) to reverse obfuscated identifiers, and returns the original trace, mapping stats, and an AI hint.
    def unmap_stack_trace(trace: str, mapping_path: str) -> Dict[str, Any]:
        """Reverse obfuscated identifiers in a stack trace using a mapping.json.
    
        Wraps `pyobfus --unmap`. Accepts the trace as a literal string (most
        useful for agent workflows where the trace is already in the chat
        buffer); for large logs, callers can pre-read the file and pass
        its contents.
    
        Args:
            trace: Obfuscated stack trace or error log as plain text.
            mapping_path: Filesystem path to a mapping.json produced by
                `pyobfus ... --save-mapping PATH`.
    
        Returns:
            Dict with keys: status, original_trace, unmapped_trace,
            mapping_stats, ai_hint.
        """
        try:
            from pyobfus.core.mapping import ObfuscationMapping
        except ImportError as e:
            return _error("PyobfusNotInstalled", str(e), "pip install pyobfus")
    
        mp = Path(mapping_path)
        if not mp.exists():
            return _error(
                "MappingNotFound",
                f"Mapping file not found: {mapping_path}",
                "Generate one with: pyobfus src/ -o dist/ --save-mapping mapping.json",
            )
    
        try:
            mapping = ObfuscationMapping.load(mp)
        except (ValueError, OSError) as e:
            return _error("InvalidMapping", str(e), "Regenerate the mapping file.")
    
        unmapped = mapping.unmap_text(trace)
        return {
            "status": "success",
            "original_trace": trace,
            "unmapped_trace": unmapped,
            "mapping_stats": mapping.stats(),
            "ai_hint": (
                "Names are reversed, but line numbers still point to the obfuscated "
                "file. Cross-reference with the original source if needed."
            ),
        }
  • MCP tool registration: @app.tool(name='unmap_stack_trace', ...) wrapping the unmap_stack_trace callable from tools.py as an MCP tool.
    @app.tool(
        name="unmap_stack_trace",
        description=(
            "Reverse obfuscated identifiers in a stack trace using a "
            "pyobfus mapping.json. Accepts the trace as plain text and the "
            "path to a mapping file produced by --save-mapping."
        ),
    )
    def _unmap(trace: str, mapping_path: str) -> Dict[str, Any]:
        return unmap_stack_trace(trace, mapping_path)
  • Input/output schema: accepts trace (str) and mapping_path (str), returns Dict with status, original_trace, unmapped_trace, mapping_stats, ai_hint.
    def unmap_stack_trace(trace: str, mapping_path: str) -> Dict[str, Any]:
        """Reverse obfuscated identifiers in a stack trace using a mapping.json.
    
        Wraps `pyobfus --unmap`. Accepts the trace as a literal string (most
        useful for agent workflows where the trace is already in the chat
        buffer); for large logs, callers can pre-read the file and pass
        its contents.
    
        Args:
            trace: Obfuscated stack trace or error log as plain text.
            mapping_path: Filesystem path to a mapping.json produced by
                `pyobfus ... --save-mapping PATH`.
    
        Returns:
            Dict with keys: status, original_trace, unmapped_trace,
            mapping_stats, ai_hint.
        """
        try:
            from pyobfus.core.mapping import ObfuscationMapping
        except ImportError as e:
            return _error("PyobfusNotInstalled", str(e), "pip install pyobfus")
    
        mp = Path(mapping_path)
        if not mp.exists():
            return _error(
                "MappingNotFound",
                f"Mapping file not found: {mapping_path}",
                "Generate one with: pyobfus src/ -o dist/ --save-mapping mapping.json",
            )
    
        try:
            mapping = ObfuscationMapping.load(mp)
        except (ValueError, OSError) as e:
            return _error("InvalidMapping", str(e), "Regenerate the mapping file.")
    
        unmapped = mapping.unmap_text(trace)
        return {
            "status": "success",
            "original_trace": trace,
            "unmapped_trace": unmapped,
            "mapping_stats": mapping.stats(),
            "ai_hint": (
                "Names are reversed, but line numbers still point to the obfuscated "
                "file. Cross-reference with the original source if needed."
            ),
        }
  • Core helper: ObfuscationMapping.reverse() looks up an obfuscated name; reverse_qualified() handles dotted segments; unmap_text() replaces all known obfuscated identifiers in text; stats() returns mapping statistics.
    def reverse(self, obfuscated_name: str) -> Optional[str]:
        """Look up an obfuscated identifier and return the original name, or None."""
        info = self.global_map.get(obfuscated_name)
        if info is None:
            return None
        return info[1] or None
    
    def reverse_qualified(self, qualified: str) -> str:
        """Unmap every dotted segment: `I0.I1` -> `MyClass.my_method`."""
        parts = qualified.split(".")
        out: List[str] = []
        for part in parts:
            out.append(self.reverse(part) or part)
        return ".".join(out)
    
    def unmap_text(self, text: str) -> str:
        """
        Replace every occurrence of a known obfuscated identifier in `text`
        with its original name. Non-obfuscated tokens pass through unchanged.
    
        Substitution is token-boundary aware (uses \\b identifier matches),
        so e.g. "I1" inside "MyI1Thing" is NOT replaced.
        """
        if not self.global_map:
            return text
    
        def _sub(match: "re.Match[str]") -> str:
            tok = match.group(1)
            original = self.reverse(tok)
            return original if original else tok
    
        return _IDENT_RE.sub(_sub, text)
    
    def stats(self) -> Dict[str, int]:
        return {
            "modules": len(self.modules),
            "original_names": sum(len(m) for m in self.modules.values()),
            "unique_obfuscated": len(self.global_map),
        }
  • Helper: ObfuscationMapping.load() reads and parses the mapping JSON file from disk, supporting version validation and global_map backfill.
    def load(cls, path: Union[str, Path]) -> "ObfuscationMapping":
        """Load mapping from a JSON file written by `save()`."""
        path = Path(path)
        if not path.exists():
            raise FileNotFoundError(f"Mapping file not found: {path}")
    
        data = json.loads(path.read_text(encoding="utf-8"))
        version = data.get("version")
        if version != MAPPING_FORMAT_VERSION:
            raise ValueError(
                f"Unsupported mapping version {version!r}. "
                f"This pyobfus expects version {MAPPING_FORMAT_VERSION}."
            )
    
        m = cls(
            root=data.get("root", ""),
            mode=data.get("mode", "single_file"),
            pyobfus_version=data.get("pyobfus_version", ""),
            created_at=data.get("created_at", ""),
            modules={mod: dict(exports) for mod, exports in data.get("modules", {}).items()},
        )
        for obf, info in data.get("global", {}).items():
            m.global_map[obf] = (info.get("module", ""), info.get("original", ""))
    
        # Backfill global_map if it was not present (forward-only format)
        if not m.global_map:
            for mod, exports in m.modules.items():
                for original, obfuscated in exports.items():
                    m.global_map.setdefault(obfuscated, (mod, original))
    
        return m
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must carry the full burden for behavioral transparency. It does not disclose whether the tool is read-only, requires any permissions, has side effects, or error handling behavior. The description only states the action and inputs, leaving gaps about what happens internally or under failure conditions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise, consisting of two sentences that deliver all necessary information without fluff. It is front-loaded with the action and efficiently explains inputs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has two simple parameters and an output schema, the description is fairly complete. It explains the purpose, inputs, and the origin of the mapping file. It could briefly mention what the output looks like (e.g., unmapped trace), but the output schema handles that. Overall, it provides sufficient context for a tool of this complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the description effectively compensates by naming both parameters and providing context: 'trace' is described as 'plain text', and 'mapping_path' is described as 'the path to a mapping file produced by --save-mapping.' This adds meaningful usage guidance beyond the bare schema titles.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Reverse obfuscated identifiers in a stack trace using a pyobfus mapping.json.' It specifies the verb (reverse), resource (stack trace), and method (mapping file), which distinguishes it from sibling tools like check_obfuscation_risks and generate_pyobfus_config.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage by stating what it does and what inputs are needed (trace as plain text, mapping file path). However, it does not explicitly state when to use this tool versus alternatives, nor does it provide exclusions or when-not-to-use guidance. Given the sibling tools, usage context is implied but not explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zhurong2020/pyobfus'

If you have feedback or need assistance with the MCP directory API, please join our Discord server