Skip to content

I Got Tired of Agents Inventing skippr.yml ​

November 2026 · Paul Hudson

Yep, that's right. I published an MCP server so Cursor would stop writing YAML that Skippr will never read.

The URL is https://mcp.skippr.io/mcp. Put it in .cursor/mcp.json, reload, and ask your agent to install skipprd and land a pipeline. No API key. No login. No npx binary to babysit.

It amazes me that after years of toiling on a distributed write-ahead log, CDC deletes, in-stream ALTER TABLE, and a Copy pipeline for billions of S3 objects, the thing that kept tripping people in the first ten minutes was a language model confidently inventing a connector called sql-server and a recover command we do not have.

I have not automated myself out of a job yet. Neither have you.

Skippr was already the short path ​

Before we get to the MCP, it is worth saying what Skippr actually is, because half the internet still hears "data tool" and pictures a SaaS dashboard, a warehouse bill, and a six-week onboarding project.

Skippr is an open Streamhouse engine. You install one Rust binary on a machine you control, or pip install skippr in Python. They run the same engine. You point a pipeline at a source and a destination, or at no destination at all if you just want the local write-ahead log. Skipprd discovers the schema, commits batches to the WAL, lands open Apache Iceberg on Parquet in a bucket you own, and gives you a live SQL engine (skipprd query) over committed Iceberg union the rows that are still in flight.

That whole loop is five commands:

bash
brew install skipprd/tap/skipprd
skipprd --workspace quickstart --storage-mode local connect data-source s3 \
  --pipeline bikehire \
  --name sample \
  --s3-bucket skippr-public-sample-data \
  --s3-prefix bike-hire
skipprd discover --pipeline bikehire
skipprd sync --pipeline bikehire --once
skipprd query --plain --sql "SELECT count(*) FROM bikehire"

Or the same thing in Python:

python
from skippr import Config, DataSourceS3, LocalStorage, Pipeline
import skippr

cfg = Config().workspace("quickstart").storage(LocalStorage())
sample = cfg.data_source(
    "sample",
    DataSourceS3(s3_bucket="skippr-public-sample-data", s3_prefix="bike-hire"),
)
cfg.pipeline("bikehire", Pipeline(data_source=sample))
cfg.save("skippr.yml")

s = skippr.Session(skippr.Config.discover().get_pipeline("bikehire"))
s.discover()
s.sync(once=True)
print(s.query("SELECT count(*) FROM bikehire"))

Connectors are not bundled into a 900 MB binary. The first time a pipeline uses Postgres, S3, Kafka, Stripe, or a local file, skipprd downloads that connector from install.skippr.io into ~/.skippr/runtime_plugins. discover samples the source and never writes to the destination. sync --once does a single pass through the WAL and exits. Leave --once off and it runs continuously.

When Upstream inevitably changes an integer into a string on a Friday evening, Skippr does not page you at 2:00 AM. It creates a sibling column in-stream and keeps moving. When you want to clean that up on Monday, skipprd migrate applies migrations/*.sql across metadata, the live WAL, and the destination without stopping ingest.

That is already fast. A working engineer can go from an empty directory to live SQL over Iceberg in a few minutes.

Then they open Cursor.

How code agents fall in the hole ​

Let me say upfront: I use Cursor and Claude every day. They are exquisite pieces of engineering. When you ask one to wire up a tool it has seen ten thousand times in training data, it feels like magic.

When you ask one to wire up Skippr cold, without a briefing, it does six things wrong in about four seconds.

First, it writes skippr.yml by hand. It invents a YAML dialect from half-remembered Airbyte, dbt, and Docker Compose files. Skippr has one way to add a source, a destination, or a schema sink: skipprd connect. connect reads skippr.yml, merges the entry, validates it, and replaces the file. If you let an agent freestyle the YAML, you get keys the engine will reject on line three.

Second, it guesses connector names. It types sql-server instead of mssql. It types skipprlake instead of skippr-lake. It types linkedin_ads instead of linked-in-ads. Close only counts in horseshoes.

Third, it puts plaintext passwords into the command line. Skippr refuses plaintext secrets. A secret field in skippr.yml must be an unresolved environment reference like ${POSTGRES_PASSWORD} (passed in single quotes so your shell does not expand it before skipprd sees it). The engine resolves it from the environment at run time. An agent that pastes "password123" into a config file is an agent you have to clean up after.

Fourth, it forgets the paired schema sink. If your destination is SkipprLake, Athena Iceberg, or DuckDB, you add the data sink and then the matching schema sink. Leave the schema-sink flags empty and skipprd connect copies them from the paired data sink. An unbriefed agent either skips the schema sink or edits the two blocks apart by hand until doctor refuses the file.

Fifth, it panics after a failed sync. If a destination credential expires halfway through a run, the agent's instinct is to delete the state directory, rewind the source, or invent a skipprd recover command. There is no recover command. Skipprd commits each batch to the WAL before landing it, and source position only advances after that commit. You fix the credential and run skipprd sync again against the same DATA_DIR. Rewinding the source is how you manufacture duplicates.

Sixth, it treats every destination as if it had the same delivery guarantee. It will happily wire a source that needs replace_partition into a destination that cannot do it, or promise you that ClickHouse is exactly-once. Skipprd refuses to start a pipeline when the destination cannot honour the source's landing policy, which saves your table, but it is still five wasted turns of chat while the agent argues with the exit code.

None of this is because the model is stupid. It is because the model is guessing.

The briefing is hosted. The engine is yours. ​

The fix is not a hosted control plane that asks for your database password and runs pipelines on our servers. The whole point of Skippr is that the binary runs on your machines, against your buckets, inside your network.

The fix is to give your code agent the exact same contract a human engineer gets from the docs, as tools it can call before it touches your terminal.

In Cursor, that is one file, .cursor/mcp.json (or ~/.cursor/mcp.json if you want it everywhere):

json
{
  "mcpServers": {
    "skippr": {
      "url": "https://mcp.skippr.io/mcp"
    }
  }
}

In Claude Code:

bash
claude mcp add --transport http skippr https://mcp.skippr.io/mcp

In VS Code and GitHub Copilot, .vscode/mcp.json:

json
{
  "servers": {
    "skippr": {
      "url": "https://mcp.skippr.io/mcp",
      "type": "http"
    }
  }
}

Windsurf, Continue, Cline, Goose, Zed, ChatGPT, Gemini CLI, Amp, OpenCode, JetBrains, Amazon Q, and Copilot Studio all take the same https://mcp.skippr.io/mcp URL. Full list is on MCP for code agents.

Notice what is not in those snippets. There is no local npx wrapper to install. There is no token to rotate. And the server does not execute skipprd. It never sees your skippr.yml, your DATA_DIR, or your credentials. Your agent calls the MCP over HTTPS to get the commands and the rules, then runs skipprd locally on your machine.

How MCP turbocharges the workflow ​

Once the server is attached, the five-minute Skippr path stops being something you copy out of a browser tab and becomes something you ask for in one sentence:

text
Install skipprd, then land local JSON into parquet with skipprd connect. Discover and sync --once.

Watch what the agent actually calls.

  1. skippr_install. It checks the install method (homebrew, install.sh, or pip) and platform (macos-arm64 or linux-x86_64). It gets told upfront that pip install skippr is the Python API and does not put skipprd on PATH.
  2. skippr_connectors. Before it guesses a connector name, it looks up data-source, data-sink, or schema-sink. The catalog is the real 73-plugin workspace catalog: Postgres, MySQL, SQL Server (mssql), MongoDB, DynamoDB, Kafka, S3, Local file (file), SkipprLake (skippr-lake), Athena Iceberg (athena-iceberg), Snowflake, BigQuery, Redshift, Databricks, DuckDB, and the SaaS and ads connectors.
  3. skippr_connect. This is the important design choice. skippr_connect does not emit YAML. It does not emit a Python snippet. You pass role, kind, pipeline, name, and a fields map for the connector flags. It returns the exact skipprd connect argv array and CLI string for the agent to run in your directory.

Ask it to connect a local JSON file source and a local Parquet sink, and skippr_connect hands back:

bash
skipprd --workspace local --storage-mode local connect data-source file \
  --pipeline events \
  --name inbox \
  --format json \
  --path ./events.json

skipprd connect data-sink file \
  --pipeline events \
  --name out \
  --format parquet \
  --output-dir ./out

Try passing "password": "hunter2" in fields for Postgres. skippr_connect refuses the call and tells the agent to pass '${POSTGRES_PASSWORD}'. The bad config never even reaches your disk.

  1. skippr_run. The agent checks the flags and exit codes (0 success, 1 runtime failure, 2 usage/config) for discover, schema, metadata, sync, query, df, serve, doctor, sql-help, and benchmark. Then it runs skipprd discover --pipeline events and skipprd sync --pipeline events --once.

That is the turbocharge. Skippr already reduced "build a streaming lakehouse pipeline" to connect, discover, sync, query. The MCP removes the gap between "the engine can do this in four commands" and "my editor knows the four commands."

The technical surface ​

Under the hood, the server speaks Streamable HTTP JSON-RPC 2.0 on https://mcp.skippr.io/mcp, protocol version 2025-03-26 (and it accepts any 2025- client version during initialize). Every tool result returns both content[].text (pretty JSON for agents that only read text blocks) and structuredContent (for agents that read typed payloads directly).

There is no session state. GET /health and GET /.well-known/mcp.json return the server card. POST /mcp handles initialize, ping, tools/list, tools/call, resources/list, resources/read, prompts/list, and prompts/get.

You can poke it from a terminal without an agent:

bash
curl -sS https://mcp.skippr.io/health
curl -sS https://mcp.skippr.io/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

We exposed fifteen tools, fifteen skippr:// resources, and five operator prompts. They map onto skipprd, not onto a second product:

ToolWhy the agent calls it
skippr_installHomebrew, install.sh, pip install skippr, supported platforms, connector cache (~/.skippr/runtime_plugins or SKIPPR_RUNTIME_PLUGIN_DIR).
skippr_configHow skipprd connect merges skippr.yml, ${NAME} secrets, local vs s3 storage mode.
skippr_deployRunning on a host, cron (sync --once), or a container. Durable DATA_DIR, WAL_STORAGE=s3, one sync writer per pipeline.
skippr_connectorsFull catalog lookup by role (data-source, data-sink, schema-sink) and hyphenated kind.
skippr_connectBuilds skipprd connect argv from role, kind, pipeline, name, and fields. Refuses plaintext secrets and unknown kinds.
skippr_runCommand reference for discover, sync, query, serve, doctor, df, schema, metadata, sql-help, benchmark.
skippr_operatedoctor, logs/skipprd.<date>.log, --log, running skipprd query alongside a live sync without starting a second writer.
skippr_transformsnamespace_fields, Hive and time partitions, flatten, in-stream transform.ops, and keyed transform.aggregates.
skippr_querySQL over <pipeline>.<namespace>: committed Iceberg union live WAL on Iceberg sinks, STREAM tail queries, skipprd serve (Iceberg REST and Flight SQL).
skippr_migratemigrations/*.sql, id + checksum journal, in-stream ALTER TABLE, why --down is only allowed for Rename and Promote.
skippr_walCommit before destination, when source offsets advance, WAL_STORAGE (disk or s3), why dynamodb is not a state store type.
skippr_correctnessDelivery classes, CDC deletes vs snapshot, landing policies (append, merge_by_key, replace_partition, replace_table), sibling columns on type conflict, ARRAY_MIXED_TYPES deadletters.
skippr_debugPlaybooks for failed syncs, missing tables, credentials, connector downloads, deadletters, crash recovery, and why Postgres in Skipprd does not use TLS.
skippr_agent_setupReady-to-paste config for all seventeen supported MCP hosts.
skippr_docsMaps a topic to its canonical https://skippr.io URL so the agent cites the real page instead of guessing.

The resources (skippr://install, skippr://cli, skippr://connectors, skippr://wal, skippr://exactly-once, skippr://cdc, skippr://landing, skippr://schema, skippr://query, skippr://migrate, skippr://transforms, skippr://operate, skippr://debug, skippr://agents, skippr://docs-map) and prompts (skippr_first_pipeline, skippr_postgres_to_iceberg, skippr_debug_failed_sync, skippr_author_migration, skippr_choose_destination_semantics) cover the same ground when a host prefers reading a resource URI or starting from a slash prompt.

Encoding correctness so the agent does not have to be a hero ​

The part I care about most in that table is skippr_correctness and skippr_wal.

Most developer tools use MCP to wrap CRUD endpoints. Data engineering is not CRUD. If an agent picks the wrong destination class or the wrong recovery step, everything still compiles, the CLI still prints green text, and you quietly corrupt a table.

When an agent calls skippr_correctness, it gets the actual matrix:

  • Exactly-once class: SkipprLake, Athena Iceberg, DuckDB, Snowflake, BigQuery, Postgres, Redshift, Databricks, Synapse, MotherDuck.
  • Overwrite-on-retry: Athena (Hive/Glue Parquet), S3, GCS, Azure Blob, Local file, SFTP.
  • At-least-once: ClickHouse, AMQP. Deduplicate downstream.
  • No retry: Stdout.

It gets told that CDC promises the current state of the source table, including deletes, while snapshot never emits deletes. It gets told that Google Analytics 4 uses replace_partition on date with lookback_days and cannot use append, whereas Stripe can. And when a sync fails at 4:55 PM, skippr_debug tells the agent the one rule that matters: do not rewind the source, do not delete DATA_DIR, fix the cause and re-run skipprd sync.

Having that in the tool call changes how useful an agent is to a working engineer. A junior engineer (or a very fast language model) no longer has to have memorised Exactly-once delivery and How sources land before they wire a pipeline. The agent checks the contract in 50 milliseconds, picks a valid pair, and links you to the page if you want to read the why.

Try it now ​

Add https://mcp.skippr.io/mcp to your editor:

json
{
  "mcpServers": {
    "skippr": {
      "url": "https://mcp.skippr.io/mcp"
    }
  }
}

Then ask your agent:

text
Call skippr_install, then use skippr_connect to land the public bike-hire S3 sample into a local pipeline, discover, sync --once, and count the rows.

The full reference is live at Skippr MCP, with host snippets on MCP for code agents, every tool on MCP tools, and the wire contract on MCP protocol. If you just want the binary first, start at Install.

Skippr is not done, till everything is done.