Skip to content

MCP Server

Octa includes a built-in MCP (Model Context Protocol) server. Run octa --mcp and Octa speaks JSON-RPC over stdin/stdout, exposing a set of tools that let an MCP-aware client (Claude Desktop, Claude Code, MCP Inspector, etc.) interact with your local data files.

The MCP server is the most popular way to wire Octa into AI workflows: instead of describing your data file to Claude in words, let Claude open it, run SQL, count rows, and convert formats on its own.

Why MCP?

Model Context Protocol is an open standard for connecting AI models to external tools and data sources. An MCP server runs locally on your machine, exposes a set of typed tools, and an MCP-aware client (the AI app) calls those tools on the model's behalf.

Octa's MCP server is fully local: the AI client connects to Octa via stdio (the AI process spawns octa --mcp as a subprocess), and no network calls leave your machine for the data operations themselves. Your files stay on disk.

The tools

Tool What it does Reference
read_table Load a file and return schema + rows as JSON → doc
tail Return the last N rows of a file → doc
sample Reproducible random N-row sample → doc
schema Return column schema only (no rows) → doc
list_tables List tables in a multi-table source → doc
count_rows Count rows in a tabular file → doc
run_sql Run a DuckDB SQL query against a file → doc
convert Convert a file from one format to another → doc
export_schema Render the schema as DDL / a model / a struct → doc
profile Per-column statistics via SUMMARIZE → doc
find_duplicates Find rows sharing key-column values → doc
fuzzy_duplicates Cluster near-duplicate rows (fuzzy) → doc
value_frequency Count per-column values (value_counts) → doc
search Match cells across every column → doc
compare_schemas Diff the column metadata of two files → doc
diff_tables Row-level diff of two files → doc
describe_file One-shot orientation snapshot → doc
validate_against_schema Validate columns against a JSON Schema → doc
unique_columns Unique columns / key candidates → doc
schema_drift Which files in a folder disagree about columns → doc
data_drift How two versions of one dataset differ → doc
check_rules Check values against a TOML rules file → doc
create_report Write a self-contained HTML profiling report → doc
fuzzy_join Join on similarity rather than equality → doc
suggest_join_keys Rank the column pairs that would join two tables → doc
pivot Reshape long <-> wide (PIVOT / UNPIVOT) → doc
batch_convert Convert many files into one format (writes) → doc
resample_timeseries Group rows into time buckets and aggregate → doc
rolling_window Rolling aggregate over the previous N rows → doc
correlation Pairwise numeric correlation matrix → doc
grep_files Grep a value across files in a directory → doc
write_table Write inline rows to a new file → doc
edit_table Add columns / set cells / insert / delete rows in place → doc
transform_columns Rename / cast / drop columns, write back → doc
anonymize Mask / scramble columns, write the result → doc
detect_pii Find likely personal-data columns → doc
detect_outliers Flag numeric outlier cells → doc
fill_missing Impute empty cells in a column → doc
drop_duplicates Remove duplicate rows → doc
union_tables Stack tables vertically → doc
join_tables Join tables on key columns → doc
partition_table One file per distinct column value → doc
diagnose_join Why two key columns do not join → doc
harmonise_schemas Rewrite a folder of files to one schema (writes) → doc
list_objects List a cloud bucket folder (S3/Azure/GCS) → doc
copy_object Copy a cloud object or folder (writes) → doc
move_object Move a cloud object or folder (writes) → doc
delete_object Delete a cloud object or folder (writes) → doc
list_db_connections List saved live-database connections → doc
list_db_tables List schemas / tables on a live connection → doc
query_db Run SQL on a live database server → doc
sync_sql SQL that would make a db table match a file (writes nothing) → doc
write_workbook Write several tables into one .xlsx (one sheet each) → doc
write_db_table Write a table into a live database (writes) → doc
copy_db_table Copy a table server-to-server (writes) → doc

Every tool that returns rows respects a configurable response row limit (default 1000) and cell byte cap (default 64 KiB), so Claude doesn't accidentally pull a 100 GB file's worth of bytes through the JSON-RPC channel. Streaming formats (Parquet, CSV, TSV) additionally honour a file-loader cap (default 5,000,000 rows). Per-call, limit: 0 lifts the response cap and unlimited: true lifts the file-loader cap. Parquet files with very many row groups fall back to a DuckDB-backed reader automatically. See Limits & Truncation for the full mechanics.

Cloud objects as input

Every read tool's path accepts a cloud object URL (s3://bucket/key, az://container/blob, gs://bucket/key) as well as a local file, with no separate tool involved: the object is downloaded to a temp file and read as usual.

Credentials come from a saved connection covering the URL, otherwise from ambient credentials (AWS_* variables, az login, gcloud auth application-default login). Cloud objects are read-only over MCP. See Cloud storage.

What this gets you

A few real-world prompts that "just work" once Octa is wired into Claude:

You: What columns does ~/data/sales-q4.parquet have?

Claude: (calls schema) region (Utf8), quarter (Utf8), amount (Float64), order_id (Int64).

You: How many rows are in users.sqlite?

Claude: (calls list_tables to find the table names, then count_rows on each) three tables: users (1,247,832 rows), orders (4,891,002 rows), products (12,408 rows).

You: What was the average order value last quarter?

Claude: (calls run_sql with SELECT AVG(amount) FROM data WHERE quarter = 'Q4') $187.42 across 423,019 orders.

You: Convert messy.xlsx to a clean Parquet file.

Claude: (calls convert with input + output paths) wrote 14,523 rows × 8 columns to messy.parquet.

You: Give me a quick profile of events.parquet.

Claude: (calls profile) 6 columns: user_id (BIGINT, 0 % null, 8.4 k distinct), amount (DOUBLE, min 0.0 / max 998.5 / mean 41.2), country (VARCHAR, 3 % null, 47 distinct)…

You: Generate a Snowflake CREATE TABLE for sales.parquet.

Claude: (calls export_schema with target: snowflake) here's the DDL: CREATE TABLE "sales" ( … ).

How it fits together

┌───────────────────────────────────────────────────────────────────┐
│ Claude Desktop / Claude Code / MCP Inspector / any MCP client     │
└─────────────────────────────┬─────────────────────────────────────┘
                              │ JSON-RPC over stdin/stdout
                  ┌───────────────────────┐
                  │  octa --mcp           │
                  │  (rmcp server)        │
                  └───────────┬───────────┘
                  ┌───────────────────────┐
                  │  FormatRegistry       │
                  │  • Parquet, CSV, JSON │
                  │  • SQLite, DuckDB     │
                  │  • Excel, SAS, …      │
                  └───────────────────────┘

The MCP server is a thin layer over Octa's existing format readers and SQL engine. Adding a new file format to Octa automatically makes it available to MCP, since the same FormatRegistry powers the GUI, the CLI, and MCP.

See also

  • Setup wires Octa into Claude Desktop, Claude Code, or MCP Inspector.
  • Tools reference covers input schemas, response formats, and examples for each tool.
  • Limits & truncation explains what happens when responses get big.
  • Examples shows worked prompts and how Claude tends to use the tools.
  • Troubleshooting covers what to do when things don't work.

Very large files

Four tools answer from the file itself rather than loading it, when the path is a local Parquet, CSV, TSV or JSON file at least large_file_min_bytes (Settings -> Performance, 10 GB by default):

Tool What changes
count_rows Exact count from the file's own metadata, no rows read
schema Columns from a DESCRIBE, no rows read
read_table Only limit rows are fetched, ordered so the page is reproducible
run_sql data becomes a view over the file, so aggregates cover every row

Those responses carry streamed: true. Everything else reads normally.

This is deliberately an opt-in per tool rather than a change to how every tool resolves its input. Tools that genuinely need the rows, union_tables, join_tables, diff_tables, correlation, detect_outliers, data_drift and the rest, would otherwise answer from a slice without saying so.

It also fixes a wrong answer, not just memory use. Before, count_rows on a file past the initial-load cap reported the cap rather than the truth, and unlimited: true tried to load the whole file to correct it.

See Large Files.