OtisDocs

Developer tools

MCP server

Connect Claude Code, Codex, Cursor, Claude, ChatGPT, or any other MCP client to Otis.

Otis runs a remote MCP server. (Model Context Protocol) is the standard way connect to outside tools. Any coding agent that supports it can connect to Otis, read live project state, ask Otis about your product's usage, and use Otis's workflows and reference material directly.

The server has one address, which is the same for every team and project:

https://app.runotis.com/mcp

This page covers how to connect an agent to the server directly, and what the server provides.

If your agent supports Agent Skills, start with Coding agents instead. The Otis skills are built on this server and add step-by-step workflows, and the Claude Code and Codex plugins connect the server for you. Connect the server by hand when your agent doesn't support skills, or when you installed the skills with the skills CLI, which doesn't connect it.

Connect your agent

If you installed the Otis plugin for Claude Code or Codex, the server is already connected. Don't add it again, or your agent gets two copies of it.

Run this from the root of your repository:

claude mcp add --transport http otis https://app.runotis.com/mcp

The command adds the server for you alone, in this repository. Add --scope project to write it to .mcp.json, which you can commit so that your team shares it. Add --scope user to use the server in every repository on your machine.

Then start Claude Code and run /mcp to sign in.

Add the server with the Codex CLI:

codex mcp add otis --url https://app.runotis.com/mcp

Codex opens a browser window for you to sign in. The command adds the server to your Codex configuration for every project on your machine.

Open this link on the machine where Cursor is installed: Add Otis to Cursor. Cursor opens and asks you to confirm the new server.

To add the server by hand, put this in .cursor/mcp.json in your repository. Use ~/.cursor/mcp.json instead to add it for every repository on your machine.

{
  "mcpServers": {
    "otis": {
      "url": "https://app.runotis.com/mcp"
    }
  }
}

Cursor asks you to sign in the first time it connects.

GitHub Copilot in VS Code uses the MCP servers that VS Code knows about. Add the server with the VS Code command line:

code --add-mcp '{"name":"otis","type":"http","url":"https://app.runotis.com/mcp"}'

The command adds the server to your VS Code user profile. To share the server with your team instead, put this in .vscode/mcp.json in your repository:

{
  "servers": {
    "otis": {
      "type": "http",
      "url": "https://app.runotis.com/mcp"
    }
  }
}

VS Code asks you to sign in the first time it starts the server.

Run this from the root of your repository:

gemini mcp add --transport http otis https://app.runotis.com/mcp

The command adds the server to .gemini/settings.json in the current directory. Add --scope user to use the server in every project on your machine.

Then start Gemini CLI and run /mcp auth otis to sign in.

Add Otis to Claude as a custom connector, in Claude on the web or in Claude Desktop. Claude's mobile apps pick it up the next time you sign in to them.

  1. Open Customize > Connectors.
  2. Click + Add, then Add custom connector.
  3. Name the connector Otis, enter https://app.runotis.com/mcp as its URL, and click Continue.
  4. Claude reads how Otis signs you in. Keep the sign-in settings it shows, and click Add.
  5. Sign in to Otis when Claude asks.

To use Otis in a conversation, click + in the message box, choose Connectors, and turn on Otis.

On a Team or Enterprise plan, an owner adds the connector once for the organization, under Organization settings > Connectors. Each member then finds Otis under Customize > Connectors and clicks Connect to sign in.

If Otis is already listed under Customize > Connectors, connect that one instead of adding a second. The Free plan allows one custom connector.

Claude shows a session's narrative as a card you can read and click through. See Session narratives as cards.

Add Otis to ChatGPT as a custom MCP server, in ChatGPT on the web. Whether you can add one depends on your ChatGPT plan and your workspace's settings. In a Business or Enterprise workspace, an admin may need to add Otis for everyone. If you don't see Add custom MCP server, ask your workspace admin.

  1. Open Plugins in ChatGPT.
  2. Select +, then Add custom MCP server.
  3. Name the server Otis and give it a short description.
  4. Under Connection, choose Public endpoint and enter https://app.runotis.com/mcp.
  5. Choose OAuth for authentication, and leave any client ID and secret empty. ChatGPT registers itself with Otis when you sign in.
  6. Read the warning, select I understand and want to continue, then select Create as a plugin.
  7. Install the plugin, and sign in to Otis when ChatGPT asks.

To use Otis in a conversation, start a new chat, type @, and choose Otis.

ChatGPT keeps the list of tools it found when you added the server. To pick up tools Otis has added since, open Otis on the Plugins page and select Refresh.

Any client that supports remote MCP servers and OAuth sign-in can connect. Add the address to your client's MCP configuration. Most clients accept this shape:

{
  "mcpServers": {
    "otis": {
      "url": "https://app.runotis.com/mcp"
    }
  }
}

Check your client's documentation for the file location and field names. A client that adds a remote server from its settings, rather than from a file, needs only the address https://app.runotis.com/mcp. Sign in to Otis when it asks.

Sign in

The first time your agent connects, it opens a browser window for you to sign in. Use the account you use for the Otis app. There are no API keys to create, and no secrets are written to your repository.

If you belong to more than one Otis team, the sign-in page asks you to choose a team. The connection then works with that team's projects. To work with another team, sign in again and choose it.

Choose a project

A connection can work with every project in your team that you have access to. How your agent chooses one depends on how many projects the team has:

  • Your team has one project. Your agent uses that project. There is nothing to choose.
  • Your team has several projects. Each tool that works on a project takes an optional project argument, which is the project's slug. Your agent finds the slug with the listProjects tool. That tool returns each project with the GitHub repositories linked to it, so your agent can pick the project whose repository matches the one it is working in.

You can also name the project in your request, for example "ask Otis about the checkout project". The slug is the last part of the project's address in the Otis app: https://app.runotis.com/<team-slug>/<project-slug>.

If a tool needs a project and your agent does not give one, the tool replies with the projects to choose from. Your agent can then call the tool again with the right one.

A few clients do not ask you to choose a team when you sign in. If you belong to several teams and use one of these clients, name the project with its team, as <team-slug>/<project-slug>.

Pin a connection to one project

To tie a connection to a single project, add the team slug and the project slug to the address:

https://app.runotis.com/mcp/<team-slug>/<project-slug>

A pinned connection always uses that project, whichever team you choose when you sign in. It refuses a project argument that names a different project. Pinning is useful when a repository belongs to one Otis project and you commit the MCP configuration for your team to share.

What the MCP server exposes

The server offers tools, prompts and resources to any connected agent.

Tools — consult Otis, read/write project state, manage ingestion, and read workflows and reference docs:

ToolWhat it does
askOtisAsk Otis — your project's product-intelligence agent — about usage, user behavior, metrics, or surfaced insights, and get back a distilled answer plus a link to the Otis app. This is what the otis skill calls; see Ask Otis about your product below.
getOtisAnswerCollect the answer to an askOtis question that Otis was still working on when the call returned
listConversationsList your project's recent Otis conversations that you can continue, with each one's conversationId and its link in the Otis app
getResultRead the rows behind an answer: page a stored query result by the result id an askOtis answer names, as CSV or JSON
queryAnalyticsAsk a question of your project's data in plain language and get the rows back as a stored result, with a result id
computeMetricsRun exact computations (rates, group-bys, ranks, significance tests) over a stored result, saved as a new result
listInsightsList the insights Otis has published for your project, oldest first. With no arguments it returns the ones your agent has not been given before
getInsightRead one insight in full: the finding, its key numbers, the evidence behind it, and suggested actions
listEntitiesList tasks, sessions, spans, agent runs, users or accounts, with the filters, search and sort order the Otis app's lists have
getEntityRead one session, task, span or agent run. An agent run, and a task, come back as numbered steps
getSpanTextRead what one span said: its messages and its tool input and output. See Reading what a span said
exportDataWrite a stored result, or every row of a filtered list, to a CSV or JSON Lines file, or a list's agent runs to an ATIF or chat JSONL file, and get a short-lived link to download it. See Exporting rows to a file
mapEvalSuiteSend the cases of an eval suite in your repository and see what the suite covers of your production traffic: each case placed on the kind of task it tests, the share of traffic and of failures the suite covers, and the most-used tasks it has no case for
getProjectMemoriesRead codebase profile, per-measurement state, declared span mappings
searchProjectMemoriesSemantic search across your Otis project
saveProjectMemoryWrite profile, measurement state, or span mappings back
removeProjectMemoryDelete an entry
getIngestionHealthCheck whether spans are arriving from prod or dev in the last 24 hours
createIngestKeyCreate an Ingest Key for the Otis SDK (returned once — store it immediately). Admin only; development keys ingest without analytics, production keys enable full analytics
getOtisWorkflowGet the step-by-step instructions for an Otis workflow: analyze, instrument, verify, status, consult (when and how to ask Otis), investigate (analyze your product's data yourself), debug (work out what happened in one session, task or agent run, and reproduce it) or fixtures (build test cases from real tasks and agent runs). See Workflows without the skills.
searchOtisDocsSearch the reference docs: the SDK reference, the Insights pages, the integration guide, and the page for each measurement
readOtisDocRead one reference doc, or one section of it, as markdown
listProjectsList the Otis projects you can use, with the GitHub repositories linked to each
renderViewShow you a session's narrative as a card you can read and click through. Listed only in agent hosts that show MCP Apps. See Session narratives as cards

askOtis, listConversations, getResult, queryAnalytics, computeMetrics, listInsights, getInsight, listEntities, getEntity, getSpanText, exportData, mapEvalSuite, renderView, getIngestionHealth, createIngestKey, and the four project memory tools take the optional project argument described in Choose a project.

Prompts — dynamic project context:

PromptContents
lens-catalogThe list of measurements available to your project, with active ones marked. It takes the same optional project argument.

Resources — reference material:

ResourceContents
otis://integration-guideSDK installation and configuration
otis://sdk/{slug}SDK reference pages (tracing, nextjs, feedback, identity, serverless, etc.)
otis://insights/{slug}Insights pages, which explain how Otis works (tasks, sessions, signals, segments, funnels, etc.)
otis://memory-taxonomyConventions for writing back to your Otis project
otis://lens-parameter-schema/{lensId}Parameter schema for a lens

The same material is available through the searchOtisDocs and readOtisDoc tools.

Ask Otis about your product

Beyond reading project state, the MCP server lets your agent consult Otis directly through the askOtis tool. Otis is the product-intelligence agent for your project — it holds the telemetry-derived insights, analytics, knowledge base, and project memory your coding agent doesn't, and runs its own (read-only) tools server-side to answer.

Ask it anything about how your product is actually used:

  • "What are users struggling with this week?"
  • "Why are people dropping off in onboarding?"
  • "What do we know about our power users?"

Otis replies with a distilled, terminal-friendly answer plus a deep link to the full view in the Otis app (charts and evidence).

With the Otis skills installed, this is the otis skill. You don't have to name it: your agent recognizes a product-usage question and uses the skill on its own. Agents without the skill call the askOtis tool directly; the skill is a convenience wrapper around it.

A few things worth knowing:

  • Be specific — include the timeframe, segment, or metric ("…in the last 14 days", "…for self-serve signups") for a sharper answer.
  • Discuss a specific insight by passing its insightId (the id in the insight's link in the Otis app, …/insights/<id>).
  • Continue a thread by passing the conversationId Otis returns on the previous call; omit it to start a fresh topic. A follow-up works from what the earlier turns found, so "narrow that to enterprise accounts" re-cuts the earlier result and does not run the investigation again. MCP conversations also appear in the Otis app.
  • Each answer ends with its evidence. After the answer Otis lists the result tables it used (an id, a row count and the column names for each), a link for each user, session or account it named, and the conversation's link in the Otis app. Read a table's rows with the getResult tool, or name its result id in a follow-up to work on it again. Results are kept for 90 days.
  • Find a conversation again with the listConversations tool. It lists the conversations you can continue, most recent first, so you can pick one up in a new coding session.
  • Read-only — over this path Otis analyzes and recalls; it won't save memories or change project state.

Working with the rows yourself

askOtis gives your agent an answer. When your agent needs the data itself, three tools return rows as CSV (or JSON with format: "json"):

  • queryAnalytics takes a question in plain language, such as "weekly active users by plan for the last 8 weeks". Otis writes and runs the query, and the rows come back as a stored result with a result id. A slow query returns the id while it is still running; getResult collects it.
  • getResult reads a stored result page by page. It works on a result id from queryAnalytics and on one named in an askOtis answer. Each page says how many rows are stored, the time window they really cover, and any warning to act on before quoting a column.
  • computeMetrics runs exact computations over a result: rates, group-bys, ranks, percentage changes, and significance tests. Your agent does not do the arithmetic itself, and the output is saved as a new result it can read or compute over again.

Use askOtis when the question needs judgment or what Otis knows about your product. Use queryAnalytics when you want the numbers to work on.

There is an hourly limit on how many queries one person can start, and another for the project as a whole. A query counts whether your agent started it with queryAnalytics or Otis started it while answering askOtis. As you get close, each queryAnalytics reply says how many queries are left. When a limit is reached queryAnalytics says when it resets, which is at the top of the next hour. askOtis still answers, from the results the conversation already has, and the block at the end of its answer starts with a Query limit: line giving the count, the limit and the reset. Reading and computing over results you already have is not limited.

Seeing what Otis has found

Otis publishes insights as it finds them. Two tools let your agent read them without asking a question first:

  • listInsights lists your project's insights in the order they were published. With no arguments it returns the ones your agent has not been given before, so "what's new in Otis?" gets only what is new. Each item has a headline, a one-line summary, and whether it is a new finding or an update to an earlier one.
  • getInsight reads one insight in full. Along with the finding it returns the result ids behind it, which getResult and computeMetrics take, and links to the sessions and tasks it quotes.

A few things worth knowing:

  • Otis remembers what each agent has seen. The position is kept for you, per project and per agent: Claude Code and Cursor each see a new insight once. To read from a point in time, pass since with a date, such as since: "2026-10-01". That read does not change what counts as new.
  • A first read goes back 7 days.
  • Updated findings come back too. When Otis finds new evidence for an earlier insight it publishes a new version, and the list shows it as updated.
  • Resolved and stale insights are left out unless you pass status: "all".
  • Find related insights by passing an insight's id as similarTo.

Looking at tasks, sessions, spans and agent runs

A number tells you something is wrong. To fix it, your agent usually needs the examples: which tasks struggled, what a failing agent run did step by step, and which spans errored. Two tools read them directly:

  • listEntities lists tasks, sessions, spans, agent runs, users or accounts, with the filters, search and sort order the Otis app's own lists have.
  • getEntity reads one session, task, span or agent run. An is one execution of an agent, from the input it receives to its final output. It comes back as a : numbered steps (S1, S2, …), one for each user message, AI call, tool call and subagent, with its status, error, model, duration and tokens, and its text cut short, under a header of counts and how the agent run ended. A task comes back as its agent runs in order, with the user's turns between them. An activity task, built from product actions alone, comes back as its list of spans.

For example, "show me the tasks on the checkout surface that struggled this week, then walk me through the worst one" becomes a listEntities call and a getEntity call:

{ "kind": "task", "filters": { "TaskOutcome": ["struggled"], "Surfaces": ["checkout"] }, "from": "2026-10-05", "order": "ErrorSpanCount.desc" }

To find agent runs instead, list them by kind. "Show me this week's agent runs from the support agent that stopped at their limit on rounds of AI calls" becomes:

{ "kind": "agent_run", "filters": { "Agent": ["support-agent"], "TerminalReason": ["max_turns"] }, "from": "2026-10-26" }

A few things worth knowing:

  • Filters must all hold, and each takes a list of accepted values or a range. {"TaskOutcome": ["struggled", "abandoned"], "Duration": {"min": 30000}} matches tasks with either outcome that ran for 30 seconds or more.
  • Pass facets: true to see what you can filter on. Surfaces, agent names, intent categories, span names and models are particular to your product, so this is how your agent learns the values, with a count for each.
  • Search by keyword or by meaning. search matches text. Add searchMode: "semantic" to rank tasks, sessions or users by meaning, or pass an id as similarTo to find the ones most like it. On spans, search matches the span's name and its user and model messages, not tool input or output.
  • Agent runs have filters of their own. Agent, RunKind, Surface, Terminal and TerminalReason each take a list of values. ToolErrorCount, IdenticalRepeatCount and RepairRetryCount take a range, and SessionId takes one id. Agent run results explains each value of Terminal and TerminalReason. A list of agent runs takes no search, and it holds only agent runs that called at least one tool.
  • Results come a page at a time. When there is more, the reply's next is the call that reads on.
  • A span list covers the last 7 days unless you pass from and to, and reads at most 9 days in one call. A date, or a time given without a time zone, is read as UTC.
  • Lists return no message text. A list row says what happened and how it went: names, statuses, errors, timings, token counts, and the summaries Otis wrote, which may quote a few words of a conversation.
  • An agent run shows its text cut short. Each text in its steps keeps its start and end: a user message or the final reply keeps up to a couple of thousand characters, and other text a few hundred. Pass text: "none" for the steps without any text. To read a step's text in full, see Reading what a span said.
  • Open a collapsed step with expand. A repeated tool call, or a subagent, shows as one line until you name it, as in expand: ["S8"], and expand: ["*"] opens every step. On an agent run, pass search to see only the steps whose text holds a word, with a little of the text around it. A task takes no search.
  • Long agent runs come a page at a time. The reply's next reads on; if the agent run gained spans in between, the next page asks your agent to read it again from the start, so step numbers never shift under it.
  • Every row links to the Otis app. A task, session, span, user or account links its own page. An agent run's row links its session's page, and getEntity on an agent run links the task it belongs to, or its session's page when it belongs to no task.

Reading what a span said

listEntities and getEntity return what each span was and how it went, and an agent run's text cut short. To read what a span said in full (the user's message, your model's response, and a tool's input and output), your agent calls getSpanText with a span id from listEntities or getEntity, or with a step: step and members as getEntity printed them, with the agent run's id for a step such as S5, or the task's id for a step such as A2:S3. Any member of the project can, and it returns what the Otis app shows them on the span's page.

A few things worth knowing:

  • A step that holds several spans has no single text. getSpanText refuses a repeat or a subagent. Open it with expand, then read one of the steps inside it.
  • The text is what Otis stored. With redaction on, that is the redacted text. The reply's redaction field says how the span was redacted, and the reply says so when the text was not redacted in full: for example, a span stored while redaction was turned off, one redacted by pattern only because the redaction model failed, or one stored before October 1, 2026, when redaction did not yet read every kind of message text.
  • A span's attributes are not returned. The reply is the four text fields.
  • An AI call that continues a conversation also returns its whole input. When Otis stored only the messages such a call added to its conversation, the reply adds a fifth field, input: every message the call sent, rebuilt from the earlier calls of the conversation, with notes on any messages it couldn't rebuild. Pass input: "stored" to read only the messages Otis stored on this call.
  • Long text comes a page at a time. Each field is cut at 4,000 characters, and the reply's next reads on from there.
  • Your agent is told the text is data. Each field comes wrapped and marked as written by your users or your model, so instructions inside it are not followed.

Session narratives as cards

Some agent hosts can show an interactive card, called an , beside an agent's reply. Claude's apps and VS Code are two of them. When your agent runs in one of them, the server lists one more tool, renderView.

renderView shows you a session's narrative as a card that you can read and click through. Your agent passes the session's id from listEntities or getEntity. The agent reads only the narrative's takeaways and a link to the session's page, so the rest of the narrative stays out of its context.

A few things worth knowing:

  • Only a session with a narrative has a card. For another session, renderView says so and gives the link to its page, and getEntity reads its summary.
  • A very long narrative gets a link instead. The card links to the session's page in the Otis app, which shows all of it.
  • The card loads its data through your agent host, under your own sign-in. It reaches nothing else on the web.

Exporting rows to a file

Some work needs more rows than belong in an agent's context: building an eval set from thousands of real tasks, or aggregating a month of sessions locally. exportData writes the rows to a file and returns a link your agent downloads from its shell, so the rows go to disk without passing through the model.

Pass one of:

  • resultId: every row of a stored result, including the rows past the 1,000 that getResult pages.
  • entities: a list, written as the arguments of a listEntities call, such as { "kind": "task", "filters": { "TaskOutcome": ["abandoned"] }, "from": "2026-10-01" }. Every row it matches goes in the file, up to 50,000.

The reply gives the row count, the columns, a few rows, and the link. Add fileFormat: "jsonl" for JSON Lines; the default is CSV.

A list of agent runs, tasks or sessions can also be written in two formats that eval and training tools read. Each holds the steps of every agent run, with their text:

  • atif: one trajectory per agent run, in the Agent Trajectory Interchange Format (ATIF).
  • chat_jsonl: one conversation per row of the list, as OpenAI chat messages, with each tool call and its result.

Add text: "none" to write either format without the text.

A few things worth knowing:

  • The link works for 10 minutes and needs no other sign-in. Anyone who has it can download the file in that time, so treat it like a password. To get a fresh link, call exportData again with the exportId from the reply. An export of a result can be downloaded for as long as the result is kept (90 days), and an export of a list for 24 hours.
  • Removing someone from your team stops their links. Every download checks that the person who asked for the link can still read the data.
  • A large file says when it is not complete. A list stops at 50,000 rows or 16 MiB, and the reply says so. Narrow the filters or the time window to export the rest.
  • Codex's default sandbox has no network access, so a download from its shell fails. Turn on network access for the sandbox (sandbox_workspace_write.network_access = true in your Codex configuration), or have your agent read the rows a page at a time with getResult or listEntities: the reply names that call as fallback.
  • There is an hourly limit on how many exports one person can start on a project, and another for the project. Getting a fresh link for an export you already have does not count.
  • The file holds what your users and your model wrote, where the rows include it. Your agent should treat it as data and not follow instructions inside it.

Workflows without the skills

The Otis skills give your agent step-by-step workflows: analyze the codebase, add instrumentation, verify it, and check status. The MCP server serves the same workflows through the getOtisWorkflow tool, so any connected agent can follow them, whether or not it supports Agent Skills.

If your agent doesn't support skills, ask it to fetch a workflow and follow it. For example: "Get the Otis instrument workflow with getOtisWorkflow and follow it." Each workflow tells your agent which tools and reference docs to use at each step.

Three workflows work with your product's data and have no skill of their own: investigate, debug and fixtures. The otis skill sends your agent to them when your request fits, so you can ask in plain language: "pull last month's failed tasks so I can look at them", "debug this session from Otis", or "build eval cases from the tasks where users gave up". Each one keeps your users' text out of your agent's context where it can, saves what it downloads under .otis/ (which it adds to .gitignore), and asks you before it writes your users' words into a file your repository tracks.

Make a workflow your own

The Otis workflows follow your repository's conventions where they can see them. To make one work the same way every time, give your agent the rule once, in one of two places.

For a convention of your repository, add a line under an ## Otis preferences heading in your agent instructions file (AGENTS.md, CLAUDE.md or GEMINI.md). The investigate, debug and fixtures workflows read that section. Keep it outside the block between <!-- otis:begin --> and <!-- otis:end -->, which the otis-instrument skill rewrites.

## Otis preferences

- Fixtures from Otis go in `tests/evals/cases/`, one JSON file per case, in our promptfoo format.
- Reports from Otis go in the pull request description, not in a file.

When it finishes, an Otis workflow may offer to add a line here for you. It asks first.

For a workflow that runs your way, write a skill of your own that fetches the Otis workflow and lists your changes after it. Because it fetches the workflow each time, it keeps up as Otis improves the instructions. A copy of the instructions doesn't. Put the file where your agent reads skills: .claude/skills/<name>/SKILL.md for Claude Code (Cursor reads this folder too), or .agents/skills/<name>/SKILL.md for Codex.

---
name: our-otis-debug
description: Debug a session, task or agent run from Otis the way this team does. Use when asked to debug a session, task or agent run from Otis.
---

Call the Otis MCP tool `getOtisWorkflow` with `name: "debug"` and follow it, with these changes:

- Write the reproduction as a failing test in `tests/regressions/`, named after the task id.
- End with a short note in `docs/incidents/`: what happened, the cause, and the test.

For a job Otis has no workflow for, your skill can start from the rules the Otis workflows follow when they read your data: ask your agent to read workflows/rules.md with readOtisDoc. They cover whose instructions come first, counting before concluding, keeping your users' text out of commits, and fetching an export.

Auth for clients that don't support MCP OAuth

API key auth for MCP isn't offered today. If your agent requires a different auth scheme and OAuth isn't an option, contact us.

The MCP server and the skills

The skills and a directly connected agent use the same server and the same project state. You can instrument your app with the skills in one agent and check its status from another agent that is connected to the server alone.

Coding agents covers the skills: how to install them, what each one does, and how they stay current. Agent skills in the SDK section covers the instrumentation steps in detail.

On this page