npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@certinia/apex-log-mcp

v2.0.1

Published

MCP server that gives AI assistants tools to analyze Salesforce Apex debug logs for performance bottlenecks, slow methods, and governor limit usage.

Readme

Apex Log MCP Server

npm version CI License Node.js

MCP Server to Analyze Salesforce Apex debug logs from your AI assistant. Finds slow methods, governor limit risks, and where a transaction spent its time.

Instead of scrolling thousands of log lines, ask what's slow and why. Uses the same parser as the Apex Log Analyzer VS Code extension.

Quick Start

Requires Node.js 22 or later. Add this to your MCP client config (claude_desktop_config.json, VS Code mcp.json, and so on):

{
  "mcpServers": {
    "apex-log-mcp": {
      "command": "npx",
      "args": ["-y", "@certinia/apex-log-mcp"]
    }
  }
}

Then ask your assistant to analyze a log. apexlog_execute_anonymous also needs an org authenticated with the Salesforce CLI.

Example Prompts

  • "Give me a summary of this debug log"
  • "Show me the 5 slowest methods in the default namespace"
  • "Are we approaching any governor limits in this transaction?"
  • "Run this Apex against my scratch org and analyze the performance"

Keeping the server connected costs ~1,232 tokens, 0.6% of a 200K context. See Token Cost.

Tools Reference

The analysis tools take an absolute path to a .log file.

Every tool returns one flat table, encoded as TOON. Nothing is repeated, and nothing is dropped to save space.

A 0 means none, not "not measured". Only what did not happen is left out: fatal errors, lost log content, query plans. Durations are in milliseconds to 3 decimal places, percentages to 1.

The server runs as a local process started by your client over stdio, no network calls and no API keys. Each log is parsed once, so follow up questions are faster.

apexlog_list_slow_operations

Ranks what a log spent its time on by self time - code units, methods, queries, searches, DML, flows and workflows in one table.

A default response returns:

  • capturedAt - {debugCategory, level}
  • operations - {debugCategory, type, name, namespace, callCount, durationTotalMs, durationSelfMs, durationSelfMaxMs, selfPercentage, soqlCount, dmlCount, soslCount, rowCount, thrownCount}
  • queryPlans - {operationRow, leadingOperationType, relativeCost, cardinality, sObjectCardinality}

Each response also gives durationTotalMs for the whole transaction, returnedSelfPercentage for the share these rows account for, and matchedCount for the rows that matched before paging.

durationSelfMaxMs is the slowest single call in a grouped row. Read it against durationSelfMs to tell one bad call from many small ones. It is absent when the row is already one call.

Two columns say what a row is, both taken straight from the log:

  • debugCategory - what Salesforce stamped on the event. It decided whether the event was logged at all, and it is the spelling apexlog_execute_anonymous takes.
  • type - the event type. The category cannot imply it: SOQL_EXECUTE_BEGIN, SOSL_EXECUTE_BEGIN and DML_BEGIN all sit under database.

Watch for ENTERING_MANAGED_PKG. It is time a package spent where the log shows nothing, and it is often most of a transaction.

sortBy: "heapSelfNetBytes" ranks by retained heap instead of time, adding that column and returnedHeapPercentage. Both are absent otherwise. The figure is signed, so a row that released more than it took reads below zero. Heap is logged only at apexCode FINER and above, so check the apexCode row of capturedAt before trusting a zero.

capturedAt gives the level each category in the returned rows was logged at.

queryPlans is what the query optimizer decided about the queries behind those rows. A relativeCost above 1 means it will not treat the query as selective. Plans are absent when the log explained none, because explain lines need database FINEST.

operationRow points at a row of operations, counting from 1. Under a namespace, callerNamespace or debugCategory grouping a row is not one query, so the plan carries the query text in name instead.

| Parameter | Type | Required | Description | | --------------- | -------- | -------- | --- | | logFilePath | string | Yes | Absolute path | | debugCategory | string[] | No | Rank only these debug log categories | | type | string[] | No | Rank only these log event types, e.g. SOQL_EXECUTE_BEGIN, DML_BEGIN, METHOD_ENTRY | | namespace | string[] | No | Rank only these namespaces | | minSelfMs | number | No | Drop operations below this self time (default: 0), whichever sortBy is used | | limit | number | No | Page size (default: 10); fewer if the page would be too large | | offset | number | No | Ranked rows to skip (default: 0). Advance it by the rows you got, which can be fewer than limit. | | groupBy | string | No | Fold repeats into one row; default name. callerNamespace attributes platform DML to the package that drove it. debugCategory folds a namespace's event types together and so states no type or name. none ranks each call on its own. A grouped durationTotalMs is what the transaction takes back if the group never runs - never sum it across rows. | | sortBy | string | No | Default durationSelfMs. heapSelfNetBytes adds that column. |

apexlog_get_summary

How long the transaction ran, where the time went, what it consumed, and whether the log is complete. Start here.

  • fatalErrors - {message, frames}
  • governorLimits - {limit, used, max}
  • limitsByNamespace - {namespace, limit, used}
  • categories - {debugCategory, level, operationCount, durationSelfMs, selfPercentage}

All thirteen governor limits are listed, zeros included.

limitsByNamespace shows what each namespace consumed. This is how you see a managed package spending your CPU time. It has no ceiling column, because a ceiling is per limit for the whole transaction and already sits in governorLimits.

categories covers all eleven, each with the level it was captured at, because the level is what a zero means:

  • database,NONE,0 - the queries were not logged.
  • database,FINEST,0 - no queries ran.
  • dataAccess,"",0 - the log's header declared no level for it, which most logs do not.

dataAccess, wave and validation are always zero. No timed event carries them.

truncated says whether the log is complete. In a partial log, every figure is a floor. Where the platform cut it, truncatedBy says how - skipped-lines for a hole, max-size for a missing tail - and skippedBytes says how much went. Both are absent when a log merely stops mid-frame.

thrownCount counts the exceptions thrown, zero included.

fatalErrors appears once per failure that ended the transaction, with the innermost three frames and a trailing … where there were more. It is the only field that says a transaction did not finish, because a fatal error need not breach any limit.

| Parameter | Type | Required | Description | | ------------- | ------ | -------- | ------------- | | logFilePath | string | Yes | Absolute path |

apexlog_list_limit_risks

The governor limits nearest their ceiling, worst first.

  • capturedAt - {debugCategory, level}
  • atRisk - {limit, used, max, usedPercentage}

threshold is reported beside the rows, so an empty table means nothing reached it rather than that the answer is missing.

capturedAt gives the level that gated each returned limit. Every limit but heap comes from apexProfiling; heapSize comes from apexCode.

| Parameter | Type | Required | Description | | ------------- | ------ | -------- | --- | | logFilePath | string | Yes | Absolute path | | threshold | number | No | Report a limit once it is this percentage consumed (default: 80) |

apexlog_execute_anonymous

Runs anonymous Apex against an authenticated org, saves the debug log locally, and returns the path. Pass that path to any analysis tool.

The response also gives the org username, its alias if set, the org type, and a summary of the run. Logs go to .apex-log-mcp/ by default - add it to your .gitignore. Production orgs are gated: see Production safety.

| Parameter | Type | Required | Description | | ------------ | ---------------- | -------- | --- | | apex | string | Yes | The anonymous Apex to be executed | | targetOrg | string | No | Alias or username of the target Salesforce org. Uses the project default if not specified. | | outputDir | string | No | Directory to save the debug log file. Defaults to .apex-log-mcp/ in the project root. | | debugLevel | string | object | No | Trace flag log levels. "default" restores the defaults; a bare level sets every category to it; an object sets only the categories named and leaves the rest unchanged. Defaults: apexCode, apexProfiling, visualforce, workflow FINE; callout, system, validation DEBUG; database FINEST; nba, wave INFO. |

An object debugLevel looks like this:

{ "database": "FINEST", "apexCode": "FINE" }

Levels are NONE, ERROR, WARN, INFO, DEBUG, FINE, FINER, FINEST.

Example prompts:

  • "Execute this Apex and show me the log: System.debug('Hello');"
  • "Run a query for all Accounts and analyze the performance"
  • "Execute this Apex with all debug levels set to FINEST"
  • "Run this Apex against my QA org with database logging set to FINEST"

Token Cost

Every request carries all four tool definitions, whether you call them or not. Each figure below is a whole definition: name, title, description, input schema and annotations.

| Tool | Tokens | | ------------------------------ | ----------------------------------------------------------- | | apexlog_list_slow_operations | ~530 | | apexlog_execute_anonymous | ~407 | | apexlog_list_limit_risks | ~150 | | apexlog_get_summary | ~145 | | Total | ~1,232 (0.6% of a 200K context), -19% vs 1.x ~1,529 |

Only the total compares with 1.x: per tool it would compare different tools, since apexlog_list_slow_operations replaced one that took three selection parameters and ranked methods where this one takes eight and ranks every timed event.

A call itself is about 15 tokens - a tool name and a log path - so what a call costs is what it returns.

Cost does not grow with the log size. The figures below are measured against a 40 KB slice of the Apex Log Analyzer sample log. On the full 19.7 MB original, apexlog_get_summary returns ~374 tokens instead of ~364, and apexlog_list_limit_risks the same ~35.

| Tool | Response | 1.x | Change | | ------------------------------ | -------- | ---- | ------ | | apexlog_get_summary | ~325 | ~293 | +11% | | apexlog_list_slow_operations | ~396 | ~408 | -3% | | apexlog_list_limit_risks | ~35 | ~84 | -58% |

Configuration

The Quick Start config gives you all four tools.

Production safety

apexlog_execute_anonymous runs arbitrary Apex, so the server identifies the org before running anything:

| Org type | Identified by | Behaviour | | ------------ | --------------------------------- | --------------------- | | sandbox | IsSandbox, no trial expiry | Runs | | scratch | IsSandbox with a trial expiry | Runs | | trial | Not a sandbox, has a trial expiry | Runs | | developer | Developer Edition | Runs | | production | Anything else | Confirmation required | | unknown | The org could not be queried | Confirmation required |

For a production org, --allow-production-orgs runs it anyway. Otherwise the server asks you to confirm, naming the org and showing the Apex. That needs a client that supports elicitation; without one the call is refused, and the message names both ways to proceed. Each confirmation authorizes one run.

An org that cannot be identified is treated as production, so a network or permissions problem can never quietly downgrade one.

Server flags

| Flag | Description | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | --allow-production-orgs | Treat production orgs like any other - no confirmation, no refusal. Only set this if production targets are intentional. | | --no-apex-execution | Refuse every Apex execution. The tool stays visible so agents know it exists. The three analysis tools are unaffected. |

For an analysis-only deployment:

{
  "mcpServers": {
    "apex-log-mcp": {
      "command": "npx",
      "args": ["-y", "@certinia/apex-log-mcp", "--no-apex-execution"]
    }
  }
}

Documentation

Contributing

See the Contributing Guide, Developing to set up your environment, and the Code of Conduct.

License

BSD 3-Clause. Copyright © Certinia Inc. All rights reserved.