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.
  • 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:

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.

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 works and verify_integration reports green, because that check only proves a page loaded. It says nothing about identity. 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.)

Confirm it in the browser console on a logged-in page:

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:

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

See Visitor identification for the details, or hand your agent the install prompt, 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.

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, and — with an account-scoped token — list and switch between the projects you own. What it cannot do via MCP is per-step visual styling, 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) — those aren't supported via MCP yet.