Skip to main content
Glama
nfunky
by nfunky
README.md
# Salesforce API Explorer

Unofficial Salesforce REST API explorer: generate an OpenAPI document from sObjects and Apex REST, then try requests. Specs stay in your browser.

- [Web app](https://sf-api-explorer.com)
- [MCP server](https://sf-api-explorer.com/mcp) · [how to connect](https://sf-api-explorer.com/mcp-guide)
- [Chrome extension](https://chromewebstore.google.com/detail/sf-api-explorer)

Not affiliated with Salesforce. Repo name: `sf-api-explorer`.

```text
sf-api-explorer/
  packages/core       shared discovery + OpenAPI generation
  packages/ui         shared explorer UI (adapter-based)
  apps/web            Next.js (OAuth Connected App + /api/sf proxy)
  apps/extension      Chrome/Edge MV3 (sid cookie, OAuth fallback)
  apps/mcp            local stdio MCP (Salesforce CLI auth)
```

**Change rule:** Salesforce API shape, OpenAPI output, and catalog filters change only in `packages/core`. Explorer screens change in `packages/ui`. Apps may only change auth, routing, and transport.

## Prerequisites

- Node.js 20+
- For the web app: a Salesforce Connected App
- For the MCP server: Salesforce CLI (`sf`) and an authorized org

## Web app (`apps/web`)

OAuth authorization-code + PKCE. Tokens stay in encrypted httpOnly cookies (6 hours from login). Try-it-out goes through `/api/sf`.

```bash
cp .env.example apps/web/.env.local
# fill SF_CLIENT_ID, SF_CLIENT_SECRET, SF_REDIRECT_URI, SESSION_SECRET
# do not commit .env.local
npm install
npm run dev
```

Open http://localhost:3000 and log in with Salesforce.

Register the Connected App callback URL so it matches `SF_REDIRECT_URI`:

- Local: `http://localhost:3000/api/auth/callback`
- Hosted: `https://<your-host>/api/auth/callback`

Keep both callbacks on the Connected App if you run locally and in production. `SESSION_SECRET` must be a 32+ byte secret used to encrypt session cookies.

## Browser extension (`apps/extension`)

Chrome and Edge, Manifest V3. By default the service worker reads the Salesforce `sid` cookie from orgs you are already logged into and calls REST as `Authorization: Bearer {sid}`. If the org rejects that session (API Access Control), the explorer can fall back to Connected App OAuth (PKCE, no client secret).

### Load unpacked

```bash
npm install
npm run dev:ext
```

WXT prints an output directory (typically `apps/extension/.output/chrome-mv3-dev`).

1. Open `chrome://extensions` (or Edge `edge://extensions`).
2. Enable Developer mode.
3. Load unpacked → select that output folder.
4. Log in to a Salesforce sandbox in another tab.
5. Click the extension icon, pick the org, open the explorer.
6. Generate the spec, try `GET /limits`, download YAML.

To rebuild without the watcher: `npm run build:ext`. Load `apps/extension/.output/chrome-mv3`.

### Permissions

The store / install prompt will say the extension can read Salesforce site data. That is required:

- `cookies` — the `sid` cookie is HttpOnly, so page scripts cannot see it.
- `tabs` — list open Salesforce tabs for the org picker.
- `identity` — optional Connected App login when the sid cookie cannot call the API.
- `storage` — session-scoped OAuth tokens (never `localStorage`).
- Host access to `*.salesforce.com`, `*.force.com`, `*.cloudforce.com`, `*.salesforce.mil` — call REST and Tooling APIs directly.

The session ID is equivalent to the logged-in user. Try-it POST/PATCH/DELETE mutates org data. Session IDs stay in memory (`sessionStorage` only stores the host name). OAuth tokens are stored in `chrome.storage.session`.

### API Access Control

Orgs that require a Connected App for API access reject cookie `sid` REST calls. The explorer then offers **Sign in with Salesforce**.

Prefer a **separate public Connected App** for the extension (PKCE, no client secret). Do not disable “Require Secret for Web Server Flow” on the web app’s confidential Connected App.

1. Copy `apps/extension/.env.example` to `apps/extension/.env`.
2. Create a Connected App for the extension. Uncheck **Require Secret for Web Server Flow**. Set `WXT_SF_CLIENT_ID` to that consumer key.
3. Add the extension Callback URL: `https://<extension-id>.chromiumapp.org/` (shown in the explorer error if login is not configured yet). Unpacked IDs are stable once you reload the extension.

Lightning `sid` cookies are often not API-capable. The extension maps `*.lightning.force.com` to `*.my.salesforce.com` and matches cookies by org id (`00D…!…`).

## MCP server

The MCP server maps an authorized Salesforce org to OpenAPI 3.1 (REST + Apex REST). Agents can search the surface, hydrate sObject field schemas, fetch one operation as a PathItem, and export YAML/JSON.

It does **not** run SOQL or create, update, or delete records. Use [Salesforce DX MCP](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_mcp.htm) (or the explorer try-it-out UI) for that.

Two ways to run it:

| Transport | Auth | Where |
|---|---|---|
| **Hosted HTTP** at `/mcp` | Same Connected App OAuth as the web app | `apps/web` |
| **Local stdio** | Salesforce CLI (`sf`) | `apps/mcp` |

### Tools

| Tool | Purpose |
|---|---|
| `sf-api-explorer_status` | Connected org and whether a catalog is cached |
| `sf-api-explorer_generate_catalog` | Discover REST + Apex REST and cache a compact catalog (call once per session) |
| `sf-api-explorer_search_sobjects` | Search sObjects by API name or label |
| `sf-api-explorer_search_operations` | Search operations (query, composite, sobjects, Apex REST) |
| `sf-api-explorer_describe_sobjects` | Hydrate field-level schemas for up to 50 sObjects |
| `sf-api-explorer_get_operation` | One OpenAPI PathItem + referenced schemas |
| `sf-api-explorer_export_spec` | Full cached document (YAML or JSON) |

Typical flow: `generate_catalog` once, then `search_sobjects` / `search_operations`, `describe_sobjects` if field schemas are missing, `get_operation` for one PathItem, `export_spec` to write or return the document.

### Hosted MCP (`apps/web`)

The Next.js app serves Streamable HTTP MCP at `/mcp`, using the **same Salesforce Connected App** and encrypted session as the explorer (cookie or `Authorization: Bearer`). MCP clients are sent through MCP OAuth, which wraps `/api/auth/login` → Salesforce → `/api/auth/callback`.

Add a remote / HTTP MCP server pointing at `https://<your-host>/mcp` (hosted: `https://sf-api-explorer.com/mcp`; local: `http://localhost:3000/mcp`). Examples:

```bash
claude mcp add --transport http sf-api-explorer https://sf-api-explorer.com/mcp
codex mcp add sf-api-explorer --url https://sf-api-explorer.com/mcp
```

Many clients also accept a project MCP JSON file:

```json
{
  "mcpServers": {
    "sf-api-explorer": {
      "type": "http",
      "url": "https://sf-api-explorer.com/mcp"
    }
  }
}
```

Codex uses TOML in `~/.codex/config.toml` instead of JSON. The first connect shows a sandbox vs production picker, then Salesforce login. After that, ask the assistant to generate the catalog and search operations.

`sf-api-explorer_export_spec` returns the document inline (no server filesystem write). Generating and describing endpoints uses the org's Salesforce API allocation.

The hosted app also has a how-to page at `/mcp-guide`. Agents can read [llms.txt](https://sf-api-explorer.com/llms.txt).

To list the hosted server on the [official MCP Registry](https://modelcontextprotocol.io/registry), publish the repo-root `server.json` after GitHub login:

```bash
mcp-publisher login github
mcp-publisher publish
```

### Local MCP (`apps/mcp`)

Stdio MCP for coding assistants (Claude Code, Codex, Cursor, and other MCP clients). Requires the [Salesforce CLI](https://developer.salesforce.com/tools/salesforcecli) on `PATH` and an authorized org (`sf org login web`). Independent of the web app: no Next.js process, no session cookie.

From the repo root, register a stdio MCP server (working directory must be the repo root so `@sf2api/core` resolves):

```json
{
  "mcpServers": {
    "sf-api-explorer": {
      "command": "npx",
      "args": [
        "tsx",
        "apps/mcp/src/index.ts",
        "--target-org",
        "DEFAULT_TARGET_ORG"
      ]
    }
  }
}
```

Replace `DEFAULT_TARGET_ORG` with a CLI alias (`sf org list`). Optional `--api-version 61.0`. Working directory must be the repo root so `@sf2api/core` resolves.

`sf-api-explorer_export_spec` writes a file under the working directory when you pass `path`.

```bash
npm run dev:mcp -- --target-org DEFAULT_TARGET_ORG
```

## Scripts

| Command | What it does |
|---|---|
| `npm run dev` | Next.js web app |
| `npm run dev:ext` | WXT Chrome extension (watch) |
| `npm run build` | Production web build |
| `npm run build:ext` | Production Chrome extension |
| `npm run zip:ext` | Zip the Chrome extension for store upload |
| `npm run dev:mcp` | Local sf-api-explorer MCP server (stdio; pass `-- --target-org ALIAS`) |
| `npm run lint` | ESLint on the web app |
| `npm test` | Unit tests for `@sf2api/core`, `@sf2api/ui`, `@sf2api/mcp`, and `@sf2api/web` |

## Version history

The web app and extension share one version. History is [CHANGELOG.md](./CHANGELOG.md). Pushing a `v*.*.*` tag creates a GitHub Release from that changelog section.

To cut a release:

1. Bump `version` in every `package.json` and in `apps/extension/wxt.config.ts`.
2. Move `[Unreleased]` notes into a new `## [x.y.z] - YYYY-MM-DD` section and update the compare links at the bottom.
3. Commit, tag `vx.y.z`, and push the tag (`git push origin vx.y.z`).

## Docker (web only)

`.dockerignore` excludes extension **source** (package.json stays so workspaces resolve). Put secrets in `apps/web/.env.local`, then:

```bash
docker compose up --build
```

The container listens on `127.0.0.1:3000`. Liveness: `GET /api/health`. For a public hostname, put a reverse proxy in front of that port and set `SF_REDIRECT_URI` (and the Connected App callback) to `https://<your-host>/api/auth/callback`.

## License

MIT. See [LICENSE](./LICENSE).