# Connect your AI agent to StepsKit (MCP)

> Per-client setup for the StepsKit MCP server: Claude Code, Cursor, Codex, Claude Desktop, and VS Code / Copilot. One server, slightly different config per tool.

*Source: https://stepskit.com/docs/ai-agents/connect*

The MCP **server is identical for every client** — it's always
`npx -y @stepskit/mcp` with your `STEPSKIT_TOKEN`. What differs is only **where
the config lives and its format**. The cleanest path is each tool's own
`mcp add` command or UI, which writes the right file for you.

## Get a token

There are two kinds of token, and which you want depends on how many projects
you work across:

| Token | Mint it at | Reaches |
| --- | --- | --- |
| **Account** | **Account settings** → **MCP Access Tokens** | Every project you own — one token per machine instead of one per repo. Your agent picks which project to write to; see [Pick a project](#pick-a-project). |
| **Project** | a project's **AI Agents** page → **Generate token** | That one project, forever. The smallest blast radius if it leaks. |

Either way, copy the token when you mint it — it's stored hashed and can't be
shown again. Everywhere below, `sk_live_...` stands for the token you copied.

## At a glance

| Client | Add it via | File | Format / key |
| --- | --- | --- | --- |
| Claude Code | `claude mcp add` or edit file | `.mcp.json` (project) / user scope | JSON · `mcpServers` |
| Cursor | Settings → Tools & MCP, or edit file | `.cursor/mcp.json` / `~/.cursor/mcp.json` | JSON · `mcpServers` |
| Codex CLI | `codex mcp add` or edit file | `~/.codex/config.toml` | **TOML** · `[mcp_servers.stepskit]` |
| Claude Desktop | edit config file | `claude_desktop_config.json` | JSON · `mcpServers` |
| VS Code / Copilot | edit file | `.vscode/mcp.json` | JSON · **`servers`** |

After adding the server, **restart your agent** so it loads, then ask it to set
up StepsKit and build a tour.

## Keep the token out of the config file

MCP config files get committed. The snippets below **reference** the token
instead of containing it, so the file is safe in git — export the real value
once, in your shell profile or a local `.env`:

```bash
export STEPSKIT_TOKEN=sk_live_...
```

How the reference works differs per client, and using the wrong mechanism fails
silently — so each snippet below uses the form its client actually documents:
Claude Code and Claude Desktop expand `${STEPSKIT_TOKEN}` from the environment,
VS Code prompts you once and stores the value itself, and Codex forwards the
variable from your shell. Cursor is the exception — it isn't documented to
expand `${VAR}`, so its config keeps the literal token and belongs in
`.gitignore`.

## Claude Code

Run this in your project — it writes the config for you:

```bash
claude mcp add stepskit --env 'STEPSKIT_TOKEN=${STEPSKIT_TOKEN}' -- npx -y @stepskit/mcp
```

The **single quotes matter** — they stop your shell from expanding `${...}` as
you run the command, so the placeholder lands in the config file and Claude Code
expands it at launch instead.

Or add it by hand to `.mcp.json` in your project root:

```json
{
  "mcpServers": {
    "stepskit": {
      "command": "npx",
      "args": ["-y", "@stepskit/mcp"],
      "env": { "STEPSKIT_TOKEN": "${STEPSKIT_TOKEN}" }
    }
  }
}
```

Pass `--scope user` to the CLI (or edit your user config) to write the entry
once for every repo instead of per project — pair that with an account token so
the one entry can reach all your projects.

**Verify:** run `/mcp` in Claude Code — `stepskit` should be listed and connected.

## Cursor

Add it from **Settings → Tools & MCP → New MCP Server**, or create
`.cursor/mcp.json` (project) / `~/.cursor/mcp.json` (global):

```json
{
  "mcpServers": {
    "stepskit": {
      "command": "npx",
      "args": ["-y", "@stepskit/mcp"],
      "env": { "STEPSKIT_TOKEN": "sk_live_..." }
    }
  }
}
```

> **Warning**
>
> Cursor isn't documented to expand `${VAR}` in its config, so this one holds
> the literal token. Keep `.cursor/mcp.json` out of git, or put the server in
> the global `~/.cursor/mcp.json` instead.

**Verify:** open Settings → Tools & MCP — `stepskit` should show a green/active
indicator and its tools listed.

## Codex CLI

Run this — it writes Codex's config for you:

```bash
codex mcp add stepskit --env STEPSKIT_TOKEN=sk_live_... -- npx -y @stepskit/mcp
```

That writes the literal token into `config.toml`. To keep it out of the file,
add the server by hand to `~/.codex/config.toml` and use `env_vars`, which
forwards the variable from your shell:

```toml
[mcp_servers.stepskit]
command = "npx"
args = ["-y", "@stepskit/mcp"]
env_vars = ["STEPSKIT_TOKEN"]
```

> **Warning**
>
> Codex uses **TOML**, not JSON. Pasting the JSON config into `config.toml` will
> fail silently — use the `[mcp_servers.stepskit]` table above. TOML has no
> variable interpolation, so a `[mcp_servers.stepskit.env]` sub-table sets
> literal values only; `env_vars` is the way to reference the environment.

**Verify:** run `codex mcp list` — `stepskit` should appear.

## Claude Desktop

Edit `claude_desktop_config.json` (macOS:
`~/Library/Application Support/Claude/`, Windows: `%APPDATA%\Claude\`):

```json
{
  "mcpServers": {
    "stepskit": {
      "command": "npx",
      "args": ["-y", "@stepskit/mcp"],
      "env": { "STEPSKIT_TOKEN": "${STEPSKIT_TOKEN}" }
    }
  }
}
```

Restart Claude Desktop — `stepskit` appears under the tools (🔌) menu.

## VS Code / GitHub Copilot

Create `.vscode/mcp.json`:

```json
{
  "inputs": [
    {
      "type": "promptString",
      "id": "stepskit-token",
      "description": "StepsKit MCP token",
      "password": true
    }
  ],
  "servers": {
    "stepskit": {
      "command": "npx",
      "args": ["-y", "@stepskit/mcp"],
      "env": { "STEPSKIT_TOKEN": "${input:stepskit-token}" }
    }
  }
}
```

VS Code prompts for the token the first time it starts the server and stores it
securely, so nothing secret lives in the file.

> **Warning**
>
> VS Code uses the root key **`servers`**, not `mcpServers`. The wrong key is
> ignored with no error message. It also doesn't substitute `${env:...}` in
> `mcp.json` — the `inputs` block above is its documented way to avoid a
> hardcoded secret.

## Any other MCP client

Anything that speaks the Model Context Protocol works. Most clients (Windsurf,
Zed, etc.) use the same `mcpServers` JSON shown above — just point them at
`npx -y @stepskit/mcp` with `STEPSKIT_TOKEN` in the env. If the client doesn't
document `${VAR}` expansion, put the literal token in and keep the file out of
git.

## Pick a project

A **project-scoped** token carries its project in the credential, so there's
nothing to choose — skip this section.

An **account** token reaches every project you own, so something has to say
which one a tool call acts on. In order, highest priority first:

1. A `project` argument on the tool call — a one-off override, available on
   `get_integration_status`, `verify_integration`, `add_allowed_domain`,
   `get_project_theme`, `update_project_theme`, `create_tour`, `list_tours`,
   `list_tooltips`, `list_surveys`, and `list_announcements`.
2. The `select_project` tool — switches projects for the rest of the session.
3. `--project <slug>` in the server's `args`.
4. `STEPSKIT_PROJECT` in the server's `env`.
5. Automatic, when the account has exactly one project.

The first two are your agent's to use: `list_projects` lists every project the
token can reach, marks the selected one, and says whether the token is pinned or
account-wide; `select_project` moves to another one (on a pinned token it
refuses, and tells you to mint an account token instead).

The next two are yours, and they set the default so nobody has to choose at all.
Prefer `--project` if you export environment variables globally — an exported
`STEPSKIT_PROJECT` follows you into every repo, which is the very footgun
account tokens remove, while an arg belongs to a single server entry:

```json
{
  "mcpServers": {
    "stepskit": {
      "command": "npx",
      "args": ["-y", "@stepskit/mcp", "--project", "acme-web"],
      "env": { "STEPSKIT_TOKEN": "${STEPSKIT_TOKEN}" }
    }
  }
}
```

Both accept a project **slug or id**. The slug is readable but changes if the
project is renamed; the id is stable. `list_projects` prints both.

If nothing resolves and you own more than one project, tools don't guess — they
return an error naming your projects and how to pick one. A project reference
that matches nothing is an error too, never a quiet fallback to another project.

## Optional: add StepsKit rules to your agent

Drop this into your project's `CLAUDE.md`, `.cursorrules`, or `AGENTS.md` so your
agent follows StepsKit conventions (stable selectors, unpublished-by-default,
the current capability boundary) even outside an MCP call:

```markdown
# StepsKit (product onboarding tours)

When this project involves StepsKit:

- Author tours through the StepsKit MCP server (`@stepskit/mcp`) — use its
  tools, don't hand-write API calls.
- Know which StepsKit project you're writing to. A token is either pinned to one
  project or scoped to the whole account. If a tool says no project is selected,
  call `list_projects` and then `select_project` — don't guess, and don't stop
  to ask when the account has only one project or `STEPSKIT_PROJECT` is set.
- Call `get_integration_status` before creating a tour. If the embed isn't
  installed, install it and run `verify_integration` (after the user loads a
  page) before creating tours.
- Pick the install form from what this project is. If it has a `package.json`
  and a bundler, install the package with whichever manager the repo uses —
  check for `pnpm-lock.yaml`, `yarn.lock` or `bun.lock` before defaulting to
  `npm install stepskit` (React/Next.js/Remix render
  `<StepsKit apiKey="..." />` from `stepskit/react`; everything else calls
  `stepskit.init()` once at the entrypoint). If it has no build step —
  WordPress, a CMS theme, Google Tag Manager, plain HTML — use the CDN
  `<script>` snippet instead, and don't add the dependency. Never leave both
  in place: two loaders mean two embed instances.
- Installing the loader is only half the install. Wire visitor identity too:
  find where this codebase resolves the authenticated user and call
  `identify({ id, email, plan, ... })` ONCE, when auth state resolves (not per
  route change) — off the `stepskit` import on the package, or
  `window.stepskit` on the snippet. Without `id`, show-once frequency capping
  falls back to a per-tab check that dies with the tab, and no targeting rule
  that tests a value can match. Pass whatever attributes the codebase already
  has, under their real field names. To CHECK the wiring landed, run
  `verify_integration` after the user loads a logged-in page: it reports
  `attributes_received`, the attribute names and types the embed is actually
  sending. Empty means visitors are still anonymous — the install isn't done.
- The npm package ships its own TypeScript types. Don't also hand-write a
  `window.stepskit` global declaration — that's only for snippet installs.
- Never wire `track()`. It ships in the snippet's method list and on the typed
  package but is a no-op today (it warns once and discards) — any
  event-tracking calls you add would be dead code.
- A target selector must resolve to exactly ONE element on every page it applies
  to. If it matches several, StepsKit renders nothing rather than decorating the
  wrong one, and flags the item in the dashboard.
- NEVER build a selector out of styling/utility classes (`.py-1.5`,
  `.grid-cols-1`, `.flex`). They look unique on the page you read them from and
  repeat on every other one. Prefer `data-testid`, `id`, or a unique component
  class; avoid brittle nth-child chains.
- When only its TEXT distinguishes an element from its siblings, append one
  trailing text hint: `#toolbar button:has-text("Export")`. Case-insensitive
  substring match, whitespace-normalized, max 100 chars, must be the last part
  of the selector.
- Set `selector_confidence: "low"` when you infer a selector from source and
  aren't sure it matches the live DOM — low-confidence steps get flagged for
  review.
- Tours are created UNPUBLISHED. Never claim a tour is live; the user reviews and
  publishes it from the StepsKit dashboard.
- Before promising a capability, call `get_capabilities`. StepsKit MCP can
  CREATE and EDIT tours (callout/modal steps; name, behavior settings, visibility
  targeting/frequency/trigger, and theme), tooltips, NPS surveys, and
  announcements; set the FOUR project-wide default themes (one each for tours,
  tooltips, announcements, and surveys — `update_project_theme` with
  `surface`) plus per-item overrides — including per-step appearance
  overrides (title/body font size, per-element alignment, button corner radius
  via `update_step` overrides); install/verify the embed; whitelist a domain;
  and list or switch between the account's projects. It CANNOT yet do per-step
  COLOR overrides, add per-step or per-tooltip media/images, publish, or read
  analytics — nor create a new project (that's a dashboard action). Say so
  plainly instead of inventing a workaround.
```
