> ## Documentation Index
> Fetch the complete documentation index at: https://docs.siftstack.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sift MCP server

> Connect AI coding agents to Sift with the Sift Model Context Protocol server.

export const MintTable = ({columns = [], rows = [], columnWidths = []}) => {
  const pushTextWithLineBreaks = (parts, text, keyBase) => {
    const segments = String(text).split(/\\n|\n/);
    segments.forEach((segment, idx) => {
      if (segment) {
        parts.push(<span key={`${keyBase}-text-${idx}`}>{segment}</span>);
      }
      if (idx < segments.length - 1) {
        parts.push(<br key={`${keyBase}-br-${idx}`} />);
      }
    });
  };
  const parseMarkdown = text => {
    if (text === null || text === undefined) return "";
    const str = String(text);
    const parts = [];
    let lastIndex = 0;
    const pattern = /(`[^`]+`|\*\*[^*]+\*\*|\*[^*]+\*|\[([^\]]+)\]\(([^)]+)\))/g;
    let match;
    while (true) {
      match = pattern.exec(str);
      if (match === null) {
        break;
      }
      if (match.index > lastIndex) {
        pushTextWithLineBreaks(parts, str.substring(lastIndex, match.index), `before-${lastIndex}`);
      }
      const fullMatch = match[0];
      if (fullMatch.startsWith("`") && fullMatch.endsWith("`")) {
        parts.push(<code key={match.index}>{fullMatch.slice(1, -1)}</code>);
      } else if (fullMatch.startsWith("**") && fullMatch.endsWith("**")) {
        parts.push(<strong key={match.index}>{fullMatch.slice(2, -2)}</strong>);
      } else if (fullMatch.startsWith("*") && fullMatch.endsWith("*")) {
        parts.push(<em key={match.index}>{fullMatch.slice(1, -1)}</em>);
      } else if (fullMatch.startsWith("[")) {
        const linkText = match[2];
        const linkUrl = match[3];
        parts.push(<a key={match.index} href={linkUrl} className="text-black-600 dark:text-black-400">
            {linkText}
          </a>);
      }
      lastIndex = pattern.lastIndex;
    }
    if (lastIndex < str.length) {
      pushTextWithLineBreaks(parts, str.substring(lastIndex), `tail-${lastIndex}`);
    }
    if (parts.length > 0) {
      return parts;
    }
    const plainParts = [];
    pushTextWithLineBreaks(plainParts, str, "plain");
    return plainParts.length ? plainParts : str;
  };
  const safeColumns = Array.isArray(columns) ? columns : [];
  const safeRows = Array.isArray(rows) ? rows : [];
  const safeColumnWidths = Array.isArray(columnWidths) ? columnWidths : [];
  const hasColumnWidths = safeColumnWidths.some(w => w !== null && w !== undefined && w !== "");
  const toCssWidth = width => typeof width === "number" ? `${width}px` : String(width);
  const getColumnStyle = idx => {
    const rawWidth = safeColumnWidths[idx];
    if (rawWidth === null || rawWidth === undefined || rawWidth === "") {
      return undefined;
    }
    const width = toCssWidth(rawWidth);
    return {
      width,
      minWidth: width
    };
  };
  const containerStyle = hasColumnWidths ? undefined : {
    overflowX: "auto"
  };
  const tableStyle = hasColumnWidths ? {
    tableLayout: "fixed",
    width: "100%"
  } : {
    width: "max-content",
    minWidth: "100%"
  };
  if (!Array.isArray(columns) || !Array.isArray(rows) || !Array.isArray(columnWidths)) {
    console.warn("MintTable received invalid props:", {
      columns,
      rows,
      columnWidths
    });
  }
  if (!safeColumns.length && !safeRows.length) {
    return null;
  }
  return <div className="mint-table-container" style={containerStyle}>
      <table style={tableStyle}>
        {hasColumnWidths && <colgroup>
            {safeColumns.map((_, idx) => {
    const style = getColumnStyle(idx);
    return <col key={idx} style={style} />;
  })}
          </colgroup>}
        <thead>
          <tr>
            {safeColumns.map((col, idx) => <th key={idx} className="text-left" style={getColumnStyle(idx)}>
                <b>{parseMarkdown(col)}</b>
              </th>)}
          </tr>
        </thead>
        <tbody>
          {safeRows.map((row, rIdx) => {
    const safeRow = Array.isArray(row) ? row : [];
    return <tr key={rIdx}>
                {safeRow.map((cell, cIdx) => <td key={cIdx} style={getColumnStyle(cIdx)}>
                    {parseMarkdown(cell)}
                  </td>)}
              </tr>;
  })}
        </tbody>
      </table>
    </div>;
};

* Ingest and discover data
* Perform analysis and transformations
* Create Sift entities

Once connected, your client lists the tools the server exposes.

Use the Sift MCP server when you want an agent to work against your Sift environment directly. The `sift-cli agent` command installs and maintains that connection for every supported client on your machine.

Supported clients:

* [Claude Code](https://claude.com/claude-code)
* [Codex](https://github.com/openai/codex)
* [Cursor](https://cursor.com)
* [OpenCode](https://opencode.ai)

## Prerequisites

Configure the Sift CLI profile before you install the integration. The MCP server reads its endpoint and API key from that profile.

1. Install the CLI and [configure a profile](/documentation/cli/sift-cli#configure-a-profile).
2. Confirm the profile works:

   ```bash theme={null}
   sift-cli ping
   ```

## Install the integration

```bash theme={null}
sift-cli agent install
```

This command detects every supported client and installs the files for each client:

* **Skills** that teach the agent how to interact with Sift by combining the MCP server, the CLI, the REST API, and the client libraries.
* **An MCP registration** that points the client at the `sift-cli` executable that ran the command.

By default, the agent has ***read-only*** access to Sift until you enable write access, and the install uses the `default` profile. Nothing is written until every target passes a preflight check, and the command refuses to overwrite a same-name skill or MCP entry that Sift does not manage.

To install against a named profile instead, pass the profile name, for example, `mission`:

```bash theme={null}
sift-cli agent install --profile mission
```

To ensure the client picks up the new registration, reload or restart the client.

### Integration files locations

<MintTable
  columns={['Client', 'Skill', 'MCP registration']}
  rows={[
['Claude Code', '`~/.claude/skills/sift`', '`claude mcp add`, user scope'],
['Codex', '`~/.agents/skills/sift`', '`codex mcp add`'],
['Cursor', '`~/.agents/skills/sift`', '`~/.cursor/mcp.json`'],
['OpenCode', '`~/.agents/skills/sift`', '`~/.config/opencode/opencode.json`']
]}
/>

The CLI detects Claude Code and Codex by their command on your `PATH`. It detects Cursor and OpenCode by their command or their configuration directory.

<Note>
  **Path dependency**: The MCP registration records the absolute path of the `sift-cli` executable that ran the install. If you move or reinstall the binary, run `sift-cli agent update` to point the clients at the new path.
</Note>

## Gated tools

By default, tools that modify or archive existing resources are disabled. When an agent calls a disabled tool, the server returns an error stating that access is disabled.

The gated tools are `update_asset`, `update_run`, `update_report`, `update_annotation`, `update_rule`, `archive_rule`, and `unarchive_rule`.

Additive writes stay available without the flag: `create_annotation`, `create_report`, `create_rule`, `create_test_report`, `append_test_measurements`, and `upload_dataset`.

<Warning>
  **Replace, not merge**: `--allow-destructive` lets an agent overwrite and archive existing Sift resources. `update_asset`, `update_run`, `update_report`, and `update_annotation` replace tags, metadata, and other collections rather than merging them. Enable it only when you intend to give the agent that access.
</Warning>

To enable gated tools for every detected client:

```bash theme={null}
sift-cli agent update --allow-destructive
```

To return every client to read-only:

```bash theme={null}
sift-cli agent update --read-only
```

If you do not specific an access mode, the default is `--read-only`.

Reload each client after changing its access mode.

## Check the integration

`sift-cli agent doctor` reports the state of the installed files and client configurations without changing them. It also checks GitHub for a newer CLI release.

```bash theme={null}
sift-cli agent doctor
```

```text theme={null}
Sift agent bundle X.Y.Z
[ok] sift-cli X.Y.Z is current
Detected: Claude Code, Codex, Cursor, OpenCode
[ok] Codex/Cursor/OpenCode skill: /Users/you/.agents/skills/sift
[ok] Claude Code skill: /Users/you/.claude/skills/sift
[ok] Claude Code MCP registration (read-only, default profile)
[ok] Codex MCP registration (read-only, default profile)
[ok] Cursor MCP registration (read-only, default profile)
[ok] OpenCode MCP registration (read-only, default profile)
All detected Sift agent integrations are healthy.
```

`doctor` reports an error when a skill is missing, a skill came from a different CLI release, an MCP registration is missing or has drifted, or the detected clients disagree on access mode or profile. Each error is followed by the command that repairs it.

To assert that every client uses a specific profile, pass the profile name, for example, `mission`:

```bash theme={null}
sift-cli agent doctor --profile mission
```

## Example prompts

Adapt these prompts to your own Assets and Runs. The installed skill tells the agent which tools to chain for each one.

Each example shows an abridged response to give you a sense of what the agent returns. Your own responses differ by client, by model, and by the data in your environment.

### Find your recent Runs

The agent resolves your identity from the API key in your profile, then filters Runs by creator.

```text theme={null}
Which runs did I create in the last week?
```

> You created three Runs in the last week: `acceptance-07`, `thermal-cycle-12`,
> and `regression-04`.

### Investigate a signal

The agent downloads the Channel data to a Parquet file, then queries it with SQL to find the samples you asked about.

```text theme={null}
Download the temperature channels for run acceptance-07 and tell me
where any of them exceeded 80C.
```

> I downloaded four temperature Channels for `acceptance-07` and queried them.
> Two exceeded 80C: `motor_temp_2` peaked at 87.4C between 00:14:22 and
> 00:14:51, and `motor_temp_3` reached 81.9C at 00:14:36.

### Open a view in Explore

The agent returns a Sift Explore link that opens your selection.

```text theme={null}
Give me an Explore link for the bus voltage channels on run acceptance-07.
```

> `https://app.siftstack.com/explore?method=single&runs=acceptance-07&channels=bus_voltage_1,bus_voltage_2`

### Record a finding

The agent creates an Annotation over the time range it identified.

```text theme={null}
Annotate the voltage drop you found on run acceptance-07 and assign it to me.
```

> I created the Annotation "Bus voltage drop" on `acceptance-07`, covering
> 00:14:22 to 00:14:51, and assigned it to you.

### Derive and upload a dataset

The agent downloads the source Channels, computes the new series with SQL, and streams the result back into Sift as a dataset.

```text theme={null}
Compute a 10-second rolling average of the temperature channels on
run acceptance-07 and upload it to the same asset.
```

> I computed a 10-second rolling average for four temperature Channels and
> uploaded the result to the Asset as `motor_temp_2_avg_10s` and three
> sibling Channels.

### Look up how Sift works

The agent searches the Sift documentation and API reference, then reads the matching pages.

```text theme={null}
How can I set up data ingestion for a new asset?
```

> I searched the Sift documentation and read the ingestion pages. Here are the
> options for getting data into a new Asset, with links to each source page.

## Update every client

Update refreshes the skill and MCP registration for every detected client and preserves the existing access mode and profile. If a newer CLI release exists, update stops and prints the installer command to run.

```bash theme={null}
sift-cli agent update
```

`sift-cli agent install` and `sift-cli agent update` act on all detected clients together. The CLI treats mixed access modes or mixed profiles as an error rather than picking one, so resolve them with an explicit access mode (`--allow-destructive` or `--read-only`) or profile, for example, `mission`:

```bash theme={null}
sift-cli agent update --profile mission
```

## Switch profiles

Point every detected client at a named profile, for example, `mission`:

```bash theme={null}
sift-cli agent update --profile mission
```

Return every detected client to the `default` profile:

```bash theme={null}
sift-cli agent update --default-profile
```

Reload each client after switching profiles.

## Configure a client manually

For a client that `sift-cli agent install` does not support, register the server yourself. Most MCP clients take a command and arguments:

```json theme={null}
{
  "mcpServers": {
    "sift": {
      "command": "sift-cli",
      "args": ["mcp"]
    }
  }
}
```

The client launches the server over stdio instead of running it interactively. Add `--profile <name>` to select a profile (for example, `mission`) and `--allow-destructive` to enable the gated tools:

```json theme={null}
{
  "mcpServers": {
    "sift": {
      "command": "sift-cli",
      "args": ["mcp", "--profile", "mission", "--allow-destructive"]
    }
  }
}
```

## Remove the integration

Uninstall removes the skill files and MCP registrations that Sift manages, and leaves everything else in place. It reports what it removed and what was not installed.

```bash theme={null}
sift-cli agent uninstall
```
