# StepsKit MCP — troubleshooting

> Fixes for common StepsKit MCP issues: the server doesn't appear, the wrong config format, an unset or empty token, no project selected, a tour was created but won't render, and low-confidence selectors.

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

## The server doesn't appear after setup

- **Restart the agent.** MCP servers load at startup — Claude Code, Cursor, and
  Codex all need a restart (or reload) after you add the config.
- **Wrong config key or format.** Cursor/Claude use `mcpServers`; VS Code/Copilot
  uses `servers`; Codex uses **TOML**, not JSON. A mismatch is ignored silently.
  See [Connect your agent](/docs/ai-agents/connect.md).
- **Run the command yourself.** Copy the `command` + `args` from your config and
  run `npx -y @stepskit/mcp` in a terminal — any error (network, Node version)
  prints directly instead of being swallowed by the client.

## "STEPSKIT_TOKEN is not set" or 401 errors

- Make sure `STEPSKIT_TOKEN` is in the server's `env` and is a full `sk_live_…`
  token.
- **The reference expanded to nothing.** If your config says
  `"STEPSKIT_TOKEN": "${STEPSKIT_TOKEN}"` (or Codex's `env_vars`), the value
  comes from your shell — and if you never exported it, the server receives an
  empty string and reports the variable as unset. Export it in your shell
  profile (`export STEPSKIT_TOKEN=sk_live_...`), then restart the agent so it
  picks up the new environment. A GUI app like Claude Desktop may not see a
  variable exported only in a terminal session.
- A 401 means the token is invalid or **revoked**. Generate a fresh one in the
  dashboard (a project's AI Agents page, or Account settings for an
  account-wide token) and update your config — tokens are shown only once.
- A 401 can also mean the **selected project no longer exists** on the account —
  it was deleted, or `STEPSKIT_PROJECT` points at something that's gone. Run
  `list_projects` to see what the token can actually reach.

## "No project selected"

An account-scoped token can reach every project you own, so when you own more
than one and nothing has said which, tools refuse to guess. The error names your
projects and how to choose:

```text
No StepsKit project selected, and this account has 3 of them.
Pick one with the `select_project` tool, pass `project: "<slug>"` on the call,
or set STEPSKIT_PROJECT=<slug> in the server's env in your MCP client config.
```

Ask the agent to run `list_projects` and then `select_project` for the one you
want — that lasts the session. To stop choosing every time, pin a default with
`--project <slug>` in the server's `args` or `STEPSKIT_PROJECT` in its `env`.
See [Pick a project](/docs/ai-agents/connect.md#pick-a-project).

A related error — `No StepsKit project matching "…"` — means the slug or id you
pinned doesn't exist on this account (often a renamed project, since slugs
follow the name). `list_projects` prints both the current slugs and the ids,
which never change.

## A tour was created but doesn't show up on my site

A created tour is an **unpublished draft** and only renders when:

1. **The embed is installed and live.** Ask the agent to run
   `verify_integration`, or check the dashboard. If it's not live, load any page
   of your running app once so the embed pings StepsKit.
2. **The domain is allowed.** Production domains must be whitelisted
   (`add_allowed_domain`); localhost is always allowed.
3. **You published it.** The agent never publishes — review and publish the tour
   from the StepsKit dashboard.
4. **It landed in the project you're looking at.** With an account-scoped token
   the tour goes to whichever project was selected at the time, which may not be
   the one open in your dashboard. Ask the agent to run `list_projects` (it
   marks the selected one) and `list_tours` to see where the tour actually is.

## The tour replays every page load, or my targeting never matches

Almost always the same cause: **the visitor is never identified.**

The embed can be live while every visitor is anonymous. With no `id`, show-once
frequency capping falls back to a per-tab check that dies when the tab closes —
so a returning visitor sees the tour again — and no audience-targeting rule
that tests a value can match. (A bare `notExists` rule is the one exception: it
matches precisely the anonymous visitors you probably didn't mean to target.)

The fastest check is your dashboard: open the project's **Integrations** page
and look at **Attributes received**. It lists the attribute names and types your
installed script is actually sending, collected from real page loads, and flags
any tour, tooltip, announcement or survey whose targeting rule references an
attribute that never arrives. An empty list is the bug.

Your agent can read the same data — `verify_integration` reports
`attributes_received` alongside the embed status.

For a live, per-page view, run this in the browser console on a **logged-in**
page:

```js
window.stepskit.validateEnvironment();
```

If `visitorId` is `null` or `userAttributes` is empty, that's the bug. The
`toursFiltered` table in the same output tells you which experiences were held
back and why.

The fix is to call `identify` wherever your app resolves the authenticated user
— once, when auth state settles:

```js
window.stepskit?.identify({ id: user.id, email: user.email, plan: user.plan });
```

See [Visitor identification](/docs/concepts/visitor-identification.md) for the
details, or hand your agent the
[install prompt](/docs/ai-agents/install-prompt.md), which wires this for you.

## Steps point at the wrong element

When the agent infers a selector from source and isn't sure it matches the live
DOM, it marks the step `selector_confidence: "low"` — these show a **"Review
selector"** badge in the canvas. Open the tour, confirm or fix the target, and
clear the badge. Prefer stable selectors (`data-testid`, `id`) in your markup to
make this rare.

## A tooltip or step shows up on an unrelated element

Almost always a selector built out of styling classes — `.py-1.5`,
`.grid-cols-1`, `.flex`. A utility class is unique on the page you picked it
from and repeats on every other page the item's URL pattern covers, so the
anchor lands on whatever carries that class next.

Items like this show an **"Ambiguous selector"** badge. Re-link them to
something that names the element rather than its styling — an `id` or a
`data-testid`.

StepsKit will not guess: when a selector matches more than one element it
renders nothing and flags the item, rather than decorating an arbitrary one.
So a disappeared tooltip and a misplaced one usually have the same cause.

## Two elements are identical except for their text

CSS can't select on text, so append one trailing `:has-text()` hint to the
target selector:

```
#toolbar button:has-text("Export")
```

The match is case-insensitive, whitespace-normalized, and a substring — so
`:has-text("Export")` matches a button reading `Export CSV`. Keep it to one
hint, at the very end of the selector, 100 characters or fewer. Everything
before it is ordinary CSS.

Prefer an `id` or `data-testid` where you can add one: text hints break when
someone edits the copy.

## The agent claims it can do something it can't

The agent _can_ edit an existing tour — its name, behavior settings, visibility
(targeting / URL pattern / screen width / frequency / trigger), theme (the
project-wide default for each experience type — tours, tooltips, announcements,
surveys — plus per-item overrides), and steps. It can also create and edit
tooltips, surveys, and announcements, apply per-step appearance overrides
(title/body font size, per-element alignment, button corner radius), and —
with an account-scoped token — list and switch between the projects you own.
What it **cannot** do via MCP is per-step _color_ overrides, upload
media/images, publish anything, read analytics,
or create a new project (that's a dashboard action). It
also can't wire custom event tracking — `window.stepskit.track()` is a no-op
today, so any `track()` calls an agent adds are dead code. If it offers one of those, point it at
`get_capabilities` (or see
[Capabilities & limits](/docs/ai-agents/capabilities.md)) — those aren't supported
via MCP yet.
