---
title: "From CLI to MCP: building agent access for the cloud"
metaTitle: "From CLI to MCP: building agent access for cloud-based AI"
slug: "from-cli-to-mcp"
blurb: "How bitdrift built a remote MCP server: why MCP now, and what we learned about authentication, tool schemas, and agent guidance along the way."
metaDescription: "How bitdrift built a remote MCP server: why MCP now, and what we learned about authentication, tool schemas, and agent guidance along the way."
cover:
  url: "/assets/posts/from-cli-to-mcp/feature-mcp-hero-full-1000x625-desktop@1x.webp"
  alt: "A session replay frame retrieved with the bitdrift MCP server"
socialThumbnail:
  url: "/assets/posts/from-cli-to-mcp/feature-mcp-hero-full-1000x625-desktop@1x.webp"
  alt: "A session replay frame retrieved with the bitdrift MCP server"
author:
  - "snow"
tags:
  - "feature"
  - "ai"
  - "engineering"
publishedDate: "2026-10-28T12:00:00Z"
modifiedDate: "2026-10-28T12:00:00Z"

---

At bitdrift, we believe the future of agentic work is long-running agents in sandboxes. Instead of a developer driving an agent from their terminal, an agent can pick up an investigation, run for hours, and keep going while nobody is watching. That shift changes what good agent access to a platform like [bitdrift](https://bitdrift.io/) needs to look like.

Earlier this year, we launched the [first iteration of agentic access](https://blog.bitdrift.io/post/introducing-bitdrift-ai) into the bitdrift platform. At the time, we made the choice to release a [public API](https://blog.bitdrift.io/post/public-api) and `bd` [CLI](https://docs.bitdrift.io/cli/quickstart), as well as a set of [skills](https://agentskills.io/) that taught AI agents how to interact with bitdrift. This has seen strong adoption, but we recently decided to also provide a remote [MCP implementation](https://blog.bitdrift.io/post/mcp): a server speaking the [Model Context Protocol](https://modelcontextprotocol.io/), which is the open standard most agent apps use to connect to external tools and data.

This post covers why we decided to release an MCP server, how it's put together, and what we learned along the way.

Want to give it a try? Add [mcp.bitdrift.io](https://mcp.bitdrift.io) to your AI client or simply install the bitdrift plugin from [our marketplace](https://github.com/bitdriftlabs/bd-skills). Setup steps for each client are in [the docs](https://docs.bitdrift.io/ai/mcp).

## Why MCP, and why now

When we first started looking at providing agent access, [MCP had a bad rep](https://github.blog/ai-and-ml/github-copilot/improving-token-efficiency-in-github-agentic-workflows/#h-replacing-github-mcp-with-github-cli) due to high token usage (tool definitions and results eating into the agent's limited context window) and poor composability, and most end users were driving agents locally, where the CLI was the more ergonomic choice.

Fast-forward to now, and the main MCP clients have implemented a number of improvements that make MCP tools less expensive up front. [Tool search](https://www.anthropic.com/engineering/advanced-tool-use) loads tool definitions only when they're needed, and code mode ([Cloudflare](https://blog.cloudflare.com/code-mode/), [Anthropic](https://www.anthropic.com/engineering/code-execution-with-mcp)) has the agent write code that calls tools, so intermediate data stays out of its context. This supports our long-term stance on MCP: **the protocol itself is not inherently bad, but early client implementations made for a sub-par end-user experience.**

The protocol has kept improving too. The [2026-07-28 spec](https://blog.modelcontextprotocol.io/posts/2026-07-28/) made MCP stateless, dropping the initial handshake and protocol-level sessions so every request stands on its own, and refined how clients register for OAuth. Both made our server simpler to build. But cheaper tools only made MCP viable. A deciding factor for us was auth, as we'll get to below.

## One API, two front doors

When we first exposed our public API we made the choice to make the export of tooling as declarative as possible. Instead of maintaining a separate public API, we added internal tooling that uses annotations on our [Protocol Buffers](https://protobuf.dev/) (Protobuf) definitions to specify what subset of our internal API should be exposed. Protobuf is a language-neutral format for defining APIs and data, and our public definitions are [on GitHub](https://github.com/bitdriftlabs/api).

We approached exposing MCP tools in a similar fashion, since a lot of tools end up being a re-export of the same APIs. Each method in our internal API (an RPC handler, in Protobuf terms) can double as a tool declaration by adding annotations that specify the tool name and description, and further annotations let us tweak the generated MCP schema. For the most part, this lets us auto-generate all the input and output schemas for our MCP tools from our internal API.

```protobuf
rpc GetTeam(GetTeamRequest) returns (GetTeamResponse) {
    option (shared.annotations.tool) = {
        name: "bd_teams_get"
        title: "Get team details"
        description: "Get a team by id, including its name and description. Requires teams read permission. Use bd_teams_list to discover IDs."
        read_only_hint: true
        destructive_hint: false
        idempotent_hint: true
        open_world_hint: false
    };
    option (.bitdrift.internal_api.unary.admin.v1.minimum_permission) = TEAMS_READ;
    option (shared.annotations.api_export_method_options).export_level = EXPORTED;
}
```

The stateless spec fits this design well. Each tool call is a self-contained request that maps directly onto one remote procedure call (RPC), so the MCP server can run like any other API service behind a load balancer. This meant that, for the most part, we were able to simply mount a new set of MCP routes on top of our existing API server.

Both the CLI and the remote MCP server are developed in our internal monorepo, alongside the backend implementation of the APIs they expose. This has allowed us to unify the pipeline for generating CLI commands and MCP tools for new APIs and centralize the guidance for both in one place. We'll talk more about "guides" below.

<Image
    alt="One set of annotated protos feeds both the CLI and the MCP server"
    altAsCaption
    large
    asset="/assets/posts/from-cli-to-mcp/bitdrift-mcp-annotated-protos@1x.webp"
/>

## What we learned

Here's what building the server taught us.

### 1. Auth belongs to the harness, not the sandbox

As more and more work happens in ephemeral cloud environments, managing auth for a CLI becomes painful: each sandboxed session needs to re-authenticate and manage the auth token within its sandbox. This works fine for someone developing on a laptop with an authenticated CLI, but in other environments you start running into the token getting lost when the VM rotates. For example, we've observed that running ChatGPT Work in a cloud runner unpredictably resets its local VM or sandbox, resulting in the token being lost.

MCP works better in these environments because the authentication context is lifted out of the session and handled by the harness, the app or service running the agent (such as Claude, ChatGPT, or Cursor). A user authenticates with the MCP server once, the harness keeps that context alive, and any session can use it without leaking secrets or having to manage authentication at all.

The latest spec also made the OAuth side simpler for us. The hard part of OAuth for an MCP server is that clients show up with no prior relationship to it, so each one has to register before a user can sign in. We support both registration paths the spec describes. With [Client ID Metadata Documents](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization) (CIMD), now the recommended path, a client's ID is simply a URL where it publishes its own metadata, so there's nothing for us to store. [Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591) (DCR), the older approach, is still there for clients that haven't moved over.

This is the one problem a CLI can't solve from inside the sandbox. Once clients had narrowed the gap on token usage, it ended up being the deciding factor for us.

### 2. Keep the agent's context small, in both directions

The thing that quickly becomes apparent when auto-generating tools from an API is that schema size varies a lot. A narrow API produces a small, precise tool schema. A general-purpose API produces a schema that every client has to load in full, whether the task needs it or not.

Our [Workflow API](https://docs.bitdrift.io/api/bitdrift_public_unary_workflows_v1_WorkflowService) is a good example. It allows constructing a state machine for log processing on device and specifying what data to collect, with a lot of match conditions and possible outputs. Exported as-is, its tool schema came to 32k tokens to create a workflow, and every client loading it would pay for the whole thing, even if they don't care about the entire API surface.

Most APIs are designed with code as the client. They tend to return everything that might be useful and leave it to the client to pluck out the interesting bits, which costs almost nothing when the client is a program. When the client is an agent, every field in the response lands in its context, so returning more than it needs both costs tokens and crowds out the rest of the investigation.

In our public API there are a number of calls that might return a lot of data, such as a large dashboard or workflow definition, or a query over a set of logs. With the CLI, agents could trim responses themselves using generic `--jq` projections (filters written in [jq](https://jqlang.org/), a command-line tool for slicing JSON), ad hoc scripts or bash invocations. Over MCP, whether the agent can do that depends on the client, so we moved that control into the tools themselves. As an iterative process during development, tools that started as direct mappings to the public API grew options for asking for less:

* **Logs and spans:** the caller picks which fields come back, from just the keys to a selected set to everything, one bounded page at a time. Very rich logs can be uploaded, and only the fields relevant to the investigation are fetched into context.
* **Charts:** the caller can ask for summary data like averages instead of receiving the full time series.

Interestingly, this makes our MCP tools feel more like a [GraphQL](https://graphql.org/) API, where the caller lists exactly which fields it wants back, than a traditional one. For an agent, asking for less is the whole point.

### 3. Awkward API semantics have to be spelled out

Some of our APIs have semantics that make sense once you know the platform, but aren't obvious from the API itself. A developer reads the docs once and remembers the quirk. An agent meets the API fresh in every session, and will keep tripping over the same quirk unless the tool warns it upfront.

Our APIs have a number of such quirks, for example:

* [bitdrift logs need to be hydrated](https://blog.bitdrift.io/post/dont-build-bitdrift#sessionBuilder) from cold storage before they can be read.
* Workflows can only be modified when they are not running.

The hydration problem we addressed by changing the exposed tools to automatically handle hydration. This gives the tools less flexibility than the API, but ends up with a simpler tool surface to reason about with less sequencing required.

For workflows, we refer callers to our guide (more on that below) to understand the workflow semantics and leverage the fact that MCP tool responses are part of the conversation to nudge the caller towards the next tool call to make progress. This is a [common pattern](https://useai.substack.com/i/167211548/every-response-is-a-prompt-injection-opportunity) and is what often differentiates the MCP tools from a traditional API.

### 4. Guidance should live next to the code it describes

In developing the CLI, a key component to making the system work well was the high-level guidance in the [`bd-cli` skill](https://docs.bitdrift.io/cli/quickstart). Not only did it explain how to use the CLI, it also explained how to compose various tasks, teaching the agent how to be productive with the platform. But the skill lived apart from the APIs it described, so every API change meant remembering to update and republish it.

As we started thinking about the MCP server, we had to decide whether it would rely on extra skills bundled as plugins (installable packages of skills and tools for agent apps), or try to be as self-sufficient as possible. We opted to introduce the concept of **guides** and move most of the guidance from the `bd-cli` skill into the monorepo, right next to the backend implementation of the APIs. Both front doors read the same data:

* The CLI: `bd guides list / bd guides read`
* The MCP server: `bd_list_guides` and `bd_read_guides`, which the server instructions tell the agent about

The tools point agents to the guides as well. When an agent works with workflows, for example, the tool tells it which guide explains the workflow semantics, and each response suggests the next call to make. So the guidance arrives when the agent needs it, not all up front.

The `bd-cli` skill now simply instructs the agent to invoke the guide command on the CLI to fetch its instructions.

Having the guidance live next to the code is what keeps it up to date. Our internal [AGENTS.md](https://agents.md/), a file of instructions for coding agents working in the repo, enforces that API changes are accompanied by guidance changes when they are too complex to be expressed in per-tool instructions, so the guides change in the same commit as the API they describe.

Here's the section of our AGENTS.md that enforces this:

```markdown
# Downstream surfaces

An exported RPC, field, or enum change usually reaches callers through other surfaces. Check
each one in the same change; the linked rules own the details:

- CLI flags and help: when `bd` exposes the behavior, its clap help states the defaults,
  limits, required flag combinations, and side effects
  ([bd-cli/AGENTS.md](../../bd-cli/AGENTS.md)).
- `bd schema` registry: a new command, or a proto newly used as CLI input or output, is
  registered in [registry.rs](../../bd-cli/bd/src/schema/registry.rs) (see "Schema registry" in
  [bd-cli/AGENTS.md](../../bd-cli/AGENTS.md)).
- CLI changelog: a user-visible CLI change adds a note to the pending file under
  [bd-cli/changelog/pending/](../../bd-cli/changelog/pending/).
- MCP tools: field comments here become MCP field docs. Update the tool description when the
  way to call the tool changes, and refresh the tool-definition snapshots (see "Validation" in
  [bd-mcp/AGENTS.md](../bd-mcp/AGENTS.md)).
- Task guides: update a guide only when the change alters a task decision, and never as the
  only place a behavior is stated ([bd-guides/AGENTS.md](../bd-guides/AGENTS.md),
  [agent-guidance.instructions.md](../../.github/instructions/agent-guidance.instructions.md)).
```

### 5. Use what only MCP can do

While tool calls map loosely back to exposed APIs, the MCP spec provides a number of other features that support tighter integration with the client harness, and we've started integrating with some of them.

The most exciting of these are **MCP Apps**, an [official MCP extension](https://github.com/modelcontextprotocol/ext-apps) that lets a server display interactive UI right in the conversation. Clients could render a time series from the raw data using third-party solutions, but MCP Apps let us provide our native rendering to users, from charts to Sankey diagrams of user flows to a fully interactive 3D session replay. The portal is still the place for deep analysis, but an inline view gives users the agent's summary and a proper visualization of the same data without leaving the conversation.

<iframe
    width="560"
    height="315"
    src="https://www.youtube.com/embed/3gjmxt5aK1g?si=9NJO2AOFS6wCQ5G4"
    title="YouTube video player"
    frameBorder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerPolicy="strict-origin-when-cross-origin"
    allowFullScreen
/>

The other commonly supported feature is **MCP Resources**, a [part of the spec](https://modelcontextprotocol.io/specification/latest/server/resources) that lets our server expose data such as files for the client to fetch, and lets clients handle it differently from tool calls. This allows our server to expose larger files in ways that may not directly impact the session context, which often gives better results. It ultimately depends on client support, but it's a nice addition to the spec that opens up a standard way of giving agents access to larger amounts of data without the direct impact on context.

We use Resources today to support downloading the guides containing agent guidelines but also for downloading large crash reports (sometimes 1MB+ for very large thread dumps) as well as for our soon-to-be-announced feature for invoking commands on live devices and collecting larger artifacts like heap profiles, CPU traces, or even screenshots from mobile devices.

## What's next

The current release primarily mirrors the API surface that was previously available via the CLI, so next steps are about enabling new avenues of agentic investigation. Top of mind are asynchronous investigations (leveraging [MCP Tasks](https://modelcontextprotocol.io/extensions/tasks/overview#tasks) and [MCP Events](https://developers.openai.com/plugins/build/mcp-events)) that allow batch processing of data within the bitdrift platform: cross-session search and aggregations without having to download large amounts of data locally.

With bitdrift's ability to dynamically control data ingestion at a very granular level, we believe that adding more analysis capabilities will let agents go from a high-level picture to very targeted data collection, performing the kinds of investigations that traditional mobile observability struggles with. This style of progressive discovery, ending in very specific data collection, is very much in the spirit of our mantra of "[querying reality](https://bitdrift.ai/)", and we believe that empowering agents to do this kind of work is the future of mobile observability.

And if you're building an MCP server for your own API, the biggest lesson we took away is this: exporting your API is the easy part. Designing what the agent actually gets to see is where the work is.

## Try it

Check out the setup instructions at [docs.bitdrift.io/ai](https://docs.bitdrift.io/ai) if you already have a bitdrift account to get started!

Already using the CLI? Nothing changes. It reads the same guides and calls the same API.

New to bitdrift? [Start a free trial](https://bitdrift.io/signup) or [contact us for a demo](https://bitdrift.io/contact-us).

---

## Frequently asked questions

### Does bitdrift have an MCP server?

Yes. bitdrift has a remote MCP server at mcp.bitdrift.io that lets AI agents query bitdrift data directly, including logs, spans, charts and workflows. It works with MCP clients such as Claude, ChatGPT, and Cursor. Agents running in the cloud can use it without installing anything locally. Setup steps for each client are at [docs.bitdrift.io/ai](https://docs.bitdrift.io/ai).

### Should I use the bitdrift CLI or the bitdrift MCP server?

Both use the same API and the same guides, so the choice depends on where your agent runs. The `bd` CLI works well for agents running in a developer's terminal. The remote MCP server is a better fit for agents in AI apps or cloud sandboxes, because the app handles authentication and nothing has to be installed. If you already use the CLI, nothing changes.

### How does authentication work with the bitdrift MCP server?

The bitdrift MCP server uses OAuth. You sign in once from your AI client, and the client keeps that sign-in active, so agent sessions can use bitdrift without handling tokens or secrets. The server supports both client registration methods in the MCP spec: Client ID Metadata Documents (CIMD) and Dynamic Client Registration (DCR).
