On this page

MCP Server

Misata ships a built-in Model Context Protocol (MCP) server. Once wired in, AI assistants, Claude Desktop, Cursor, Windsurf, Zed, Continue, can generate realistic synthetic datasets on your behalf without you writing a single line of Python.

TL;DR: type "generate a fintech fraud dataset with 10k customers" in Claude, and Claude calls Misata, writes the CSVs to disk, and shows you a preview.


Install#

Shell
pip install "misata[mcp]"

This pulls in the mcp Python SDK and jsonschema for the YAML validation tool. The server binary is registered as a console script:

Shell
which misata-mcp   # → /path/to/venv/bin/misata-mcp

Wire it into your AI assistant#

Claude Desktop#

Edit ~/Library/Application Support/Claude/claude_desktop_config.json on macOS, or %APPDATA%\Claude\claude_desktop_config.json on Windows:

JSON
{
  "mcpServers": {
    "misata": {
      "command": "misata-mcp"
    }
  }
}

Restart Claude Desktop. Misata will appear in the tools list, look for the plug icon in the input area.

Cursor#

Open Settings → MCP (or .cursor/mcp.json) and add:

JSON
{
  "misata": {
    "command": "misata-mcp"
  }
}

Windsurf#

Add to ~/.codeium/windsurf/mcp_config.json:

JSON
{
  "mcpServers": {
    "misata": {
      "command": "misata-mcp"
    }
  }
}

Zed / Continue / other editors#

The command is always misata-mcp. Refer to your editor's MCP documentation for the exact config file path. The pattern is identical across all clients.


What the agent can do#

The server exposes nine tools:

ToolPurpose
generate_from_schemaPrimary. The agent designs a schema dict (any domain); Misata generates CSVs and returns an integrity proof (per-relationship orphan counts, exact roll-ups, seeded reproducibility), a coherence score, and — when a __domain__ is declared — a domain-validation pass
generate_datasetStory-based generation: Misata's own parser designs the schema from one sentence. Also returns a coherence score
list_domainsList all 18 built-in domains with a sample story for each
preview_storyDetect domain, scale, locale, and table layout: zero rows generated
inspect_schemaReturn the full schema (tables, columns, FK relationships) as structured JSON
audit_datasetRun the coherence audit on any folder of CSVs: backwards timestamps, totals that don't reconcile, near-constant columns, scored 0-100
validate_domainFlag physiologically or financially impossible values in a folder of CSVs (clinical, financial)
validate_yamlTwo-layer validation (structural JSON Schema + semantic coherence checks) of a misata.yaml
seed_databaseInsert generated rows into a real Postgres or SQLite database. Plans by default; writes only on apply=true

The division of labour is deliberate: agents are good at deciding that a veterinary clinic needs a species column; Misata is good at guaranteeing the math, FK integrity, exact aggregates, declared distributions, byte-identical reruns under a seed. generate_from_schema's tool description teaches the agent the full schema-dict language — per-table __rows__, the distribution set, formulas with @parent.column references, exact roll-ups, pattern codes, correlations, ICC cluster effects, state machines, time-series autocorrelation, plus the schema-level declarations (__outcome_curves__, __group_shares__, __waterfalls__, __stock_flows__, __lifecycles__, and the __duplicates__ / __typos__ / __outliers__ trio) — so any MCP-capable model can drive most of what the engine supports without reading these docs first.

Both generation tools write CSVs to a temp directory by default. The agent gets back file paths and a small preview, it never has to dump millions of rows into the chat context.


Prompts to try#

Paste any of these into your assistant. Each one is chosen to push a different part of the engine.

One sentence, let the parser design it

"Generate a fintech fraud dataset with 10k customers and a 2% fraud rate."

"Give me an HR system for 200 employees in Germany, with a payroll table."

"Build a food delivery dataset: 300 restaurants, 2k couriers, 40k orders over the last quarter."

Declare the outcome, not the rows

"Generate SaaS subscription data where MRR grows from $50k in January to $200k in December, with a dip in Q3 and a spike in November. Starter is 20% of revenue, Pro 50%, Enterprise 30%."

"Ecommerce orders for 2026 where Q4 revenue lands at exactly $2.4M and the refund rate climbs from 1% to 4% after a pricing change in September."

"A logistics dataset where on-time delivery is 87% overall, breaching the SLA in Q3, and worst in the Nordics region."

Ask for structure that survives a second look

"Design a clinical trial: 400 patients across 12 sites, three arms, HbA1c measured at 6 visits each. The arms need different treatment effects, measurements within a patient should be autocorrelated, and sites should show a random-intercept effect. Then audit it."

"Generate an order-fulfilment dataset where every order moves through placed → paid → shipped → delivered, with a small fraction refunded, and status can never skip a step."

Prove data you already have

"Audit the CSVs in ./seed_data and tell me the coherence score and the worst three findings."

"Validate the dataset in ./trial_export against clinical domain rules."

Fill a real database

"Plan a seed of my dev database at postgresql://localhost/myapp_dev, then show me the plan before writing anything."

Dirty data on purpose

"Generate a contacts table with exactly 60 duplicate rows and 120 typo'd city values, so I can test a cleaning pipeline against a known count."


Getting the best results#

Let the agent design the schema, you describe the outcome. The single biggest difference between a mediocre result and a good one is whether the request states what has to be true of the data — a period total, a rate, a share, a correlation — rather than a min/max on a column. "Revenue grows to $200k by December" is an __outcome_curves__ declaration the engine hits exactly. "Revenue between $0 and $200k" is not.

Read the coherence score before you use the data. Every generate_from_schema and generate_dataset response now carries a coherence block with a score from 0 to 100 and its findings. A score in the 90s means the data holds up. A lower score almost always means the schema is missing realism structure — correlations between columns that co-vary, per-group profiles, time_series for longitudinal data, a __state_machine__ for an entity that moves through stages. Ask the agent to add that and regenerate, rather than accepting the first pass.

Use the right tool for the shape of the request:

The request is…Tool
one sentence, common domaingenerate_dataset
specific tables, columns, or declared outcomesgenerate_from_schema
"will this schema even work?" before a big runvalidate_yaml
checking data that already existsaudit_dataset, validate_domain
loading a real dev databaseseed_database (plan first)

Set a seed if you want the run to be repeatable. The same schema and seed produce byte-identical output on any machine. Omit it and each run is fresh.

For anything touching a real database, expect two calls. seed_database plans by default and writes nothing; it only inserts after a second call with apply=true, and it refuses to touch a table that already has rows unless you pick truncate (destructive) or append.


A full worked flow#

What a good agent session looks like end to end:

  1. You: "I need demo data for a Series B logistics company selling into the Nordics. On-time delivery should be 87% and degrading in Q3."
  2. Agent calls generate_from_schema with a warehouses / shipments / carriers schema and an __outcome_curves__ declaration for the on-time rate.
  3. Response comes back: files written, integrity.verified: true, coherence.score: 78, with a finding that carrier_cost is near-constant.
  4. Agent adds a lognormal distribution and a correlation between distance_km and carrier_cost, regenerates. coherence.score: 94.
  5. Agent calls audit_dataset on the output to confirm, then reports: "Generated and verified. On-time delivery is 87.0% overall, 81% in Q3. Every foreign key resolves. Coherence 94/100."

Tool reference#

list_domains#

Returns a list of all 18 domain objects, each with name, keywords, and sample_story.

Input: none

Output:

JSON
{
  "ok": true,
  "domains": [
    {
      "name": "saas",
      "keywords": ["saas", "subscription", "mrr", "arr", "churn"],
      "sample_story": "A SaaS startup with 5k users, 20% monthly churn, MRR $50k"
    },
    ...
  ]
}

preview_story#

Parses a story and returns domain detection, scale, locale, and table layout, no rows generated.

Input:

JSON
{
  "story": "A fintech startup with 10k customers, 3% fraud rate",
  "rows": 10000
}

Output:

JSON
{
  "ok": true,
  "domain": "fintech",
  "domain_confidence": "high",
  "matched_keywords": ["fintech", "fraud"],
  "scale_params": {"users": 10000},
  "locale": null,
  "table_preview": [
    {"name": "customers",     "rows": 10000, "columns": 9},
    {"name": "accounts",      "rows": 10000, "columns": 6},
    {"name": "transactions",  "rows": 80000, "columns": 8}
  ],
  "total_rows": 100000,
  "temporal_events": [],
  "warnings": []
}

inspect_schema#

Returns the full parsed schema as structured data, including table names, column definitions, and FK relationships.

Input:

JSON
{
  "story": "A SaaS company with 5k users",
  "rows": 5000
}

Output:

JSON
{
  "ok": true,
  "tables": [
    {
      "name": "users",
      "rows": 5000,
      "columns": [
        {"name": "user_id",    "type": "int",         "unique": true},
        {"name": "email",      "type": "email"},
        {"name": "plan",       "type": "categorical", "values": ["free", "pro", "enterprise"]},
        {"name": "created_at", "type": "datetime"}
      ]
    },
    ...
  ],
  "relationships": [
    {"from": "subscriptions.user_id", "to": "users.user_id"}
  ],
  "outcome_curves": [
    {"table": "subscriptions", "column": "mrr", "curve_points": [...]}
  ]
}

generate_from_schema#

The primary tool: the agent supplies a schema dict it designed itself, Misata generates the data and proves the integrity. Supports per-table row counts, the full distribution set, derived columns (formula, including cross-table @parent.column references), exact roll-ups, FK declarations, and pattern-based codes.

Input:

JSON
{
  "schema": {
    "customers": {
      "__rows__": 500,
      "id":             {"type": "integer", "primary_key": true},
      "name":           {"type": "string"},
      "lifetime_value": {"rollup": {"from_table": "orders", "fk": "customer_id",
                                    "agg": "sum", "column": "total"}}
    },
    "orders": {
      "__rows__": 5000,
      "id":          {"type": "integer", "primary_key": true},
      "customer_id": {"type": "integer", "foreign_key": {"table": "customers", "column": "id"}},
      "quantity":    {"type": "integer", "min": 1, "max": 5},
      "unit_price":  {"type": "float", "distribution": "lognormal", "mean": 40, "std": 25},
      "total":       {"formula": "quantity * unit_price"},
      "placed_at":   {"type": "datetime"}
    }
  },
  "seed": 7
}

Output: the same file/preview envelope as generate_dataset, plus an integrity proof, a coherence score, and — when the schema declares a __domain__ — a domain-validation pass:

JSON
{
  "integrity": {
    "verified": true,
    "status": "verified",
    "declared": 1,
    "checked": 1,
    "relationships": [
      {"relationship": "orders.customer_id → customers.id", "intact": true, "orphans": 0}
    ]
  },
  "coherence": {
    "available": true,
    "score": 96.0,
    "clean": true,
    "summary": "Coherence: clean — no reader-visible contradictions.",
    "findings": [],
    "findings_truncated": 0
  }
}

The agent can tell you: "Generated and verified: 0 orphaned foreign keys, every customer's lifetime_value reconciles exactly with their orders, and the data scores 96/100 for coherence with no reader-visible contradictions."

If the coherence score is low, the findings array names what a reader would catch — a column that is 98% one value, a total that does not equal quantity * unit_price, a shipped_at before its ordered_at. Fix those in the schema and regenerate.


generate_dataset#

Generates a full dataset and writes one CSV per table to output_dir (defaults to a temp directory).

Input:

JSON
{
  "story": "Ecommerce store — 5k customers, Black Friday spike, Q1 slump",
  "rows": 5000,
  "seed": 42,
  "output_dir": "/tmp/misata_out"
}

Output:

JSON
{
  "ok": true,
  "output_dir": "/tmp/misata_out",
  "files": [
    {"table": "customers",   "path": "/tmp/misata_out/customers.csv",   "rows": 5000},
    {"table": "products",    "path": "/tmp/misata_out/products.csv",    "rows": 200},
    {"table": "orders",      "path": "/tmp/misata_out/orders.csv",      "rows": 15000},
    {"table": "order_items", "path": "/tmp/misata_out/order_items.csv", "rows": 45000}
  ],
  "preview": {
    "customers": [
      {"customer_id": 1, "email": "alice@example.com", "country": "US", ...},
      ...
    ]
  }
}

The agent can tell you: "Generated 65,200 rows across 4 tables. Files are at /tmp/misata_out/. Here's a preview of the customers table…"

Every files entry also carries a sample of the first few rows, and the response carries a coherence block identical in shape to generate_from_schema's.


audit_dataset#

Runs the coherence audit on a folder of CSVs — one per table — and scores it 0 to 100. Works on anything: a folder Misata just wrote, a folder a person built by hand, another tool's output.

Input:

JSON
{ "dataset_dir": "/tmp/misata_out", "top_findings": 20 }

Output:

JSON
{
  "ok": true,
  "score": 71.0,
  "clean": false,
  "summary": "Coherence: 3 findings (1 high, 2 medium).",
  "tables_audited": ["customers", "orders", "order_items"],
  "findings": [
    {"severity": "high", "table": "orders", "column": "shipped_at",
     "finding": "312 rows shipped before they were ordered"},
    {"severity": "medium", "table": "orders", "column": "total",
     "finding": "total does not equal quantity * unit_price for 88 rows"}
  ],
  "findings_truncated": 1
}

Checks include: timestamps that run backwards, derived columns that do not reconcile with their inputs, geographic fields that disagree (city / state / postcode / country), near-constant columns, filler text, out-of-scale numerics.


validate_domain#

Checks a folder of CSVs for values that are physiologically or financially impossible for a stated domain.

Input:

JSON
{ "dataset_dir": "/tmp/trial_export", "domain": "clinical" }

domain must be one of clinical_trial, clinical, financial, fintech — an unknown domain is refused, not silently passed.

Output:

JSON
{
  "ok": true,
  "domain": "clinical",
  "passed": false,
  "summary": "1 error, 2 warnings.",
  "errors": [
    {"table": "visits", "column": "hba1c", "message": "4 values outside 4-14%"}
  ],
  "warnings": [],
  "tables_checked": ["patients", "visits"]
}

Built-in ranges — clinical: HbA1c 4-14%, BMI 10-80, systolic BP 60-260, age 0-130, glucose 2-40, cholesterol 1-20, hemoglobin 3-25. Financial: price >= 0, discount 0-1, rate -1 to 100.


seed_database#

Fills a live Postgres or SQLite database with data read from its own schema. Plans by default — nothing is written until a second call with apply=true.

Input (plan):

JSON
{ "db_url": "postgresql://localhost/myapp_dev", "rows": 500 }

Output (plan):

JSON
{
  "ok": true,
  "applied": false,
  "insert_order": ["accounts", "users", "invoices"],
  "foreign_keys": 2,
  "tables": [
    {"name": "accounts", "existing_rows": 0, "will_insert": 200},
    {"name": "users",    "existing_rows": 0, "will_insert": 1240},
    {"name": "invoices", "existing_rows": 0, "will_insert": 4800}
  ],
  "note": "Plan only, nothing was written. To write, call again with apply=true."
}

Then { "db_url": "...", "apply": true } performs the insert (parents before children) and queries the database back to confirm every foreign key resolves. If a target table already has rows, the write is refused unless you pass truncate=true (wipes it, destructive) or append=true (keeps it, seeds only empty tables). Requires the db extra: pip install "misata[db]".


validate_yaml#

Three-layer validation of a misata.yaml string: structural (JSON Schema), semantic (FK consistency, formula references, distribution params), then feasibility (declarations that each parse but cannot all hold at once — shares that sum past 1.0, a period total below the sum of its parts).

Input:

JSON
{
  "yaml_text": "tables:\n  users:\n    rows: 1000\n    columns:\n      ..."
}

Output (valid):

JSON
{
  "ok": true,
  "valid": true,
  "errors": [],
  "warnings": []
}

Output (invalid):

JSON
{
  "ok": true,
  "valid": false,
  "errors": [
    "tables[0].columns[2]: 'distribution' must be one of: uniform, normal, lognormal, ...",
    "tables[1].columns[0]: formula references column 'gross_pay' which is not defined in this table"
  ],
  "warnings": [
    "tables[0]: no primary key column — consider adding a unique int column"
  ]
}

Error handling#

All nine tools return a consistent {"ok": true/false, ...} envelope. When something goes wrong the agent receives a structured error instead of a Python traceback, and can take corrective action:

JSON
{
  "ok": false,
  "error": "ValueError",
  "message": "No domain could be detected from the story.",
  "suggestion": "Name the domain explicitly — e.g. add 'fintech', 'saas', or 'ecommerce' to your story."
}
JSON
{
  "ok": false,
  "error": "OSError",
  "message": "[Errno 13] Permission denied: '/protected/output'",
  "suggestion": "Check that the output_dir path exists and is writable, or omit it to use a temp directory."
}

The agent can read the suggestion field and reformulate its next call without surfacing raw Python errors to the user.


How it works#

The server is a thin protocol shim over Misata's existing public API. Each tool maps to library functions you could call yourself:

Text
list_domains          →  StoryParser.DOMAIN_KEYWORDS
preview_story          →  misata.preview()
inspect_schema         →  misata.parse()
generate_dataset       →  misata.generate() + coherence_audit() + to_csv()
generate_from_schema   →  misata.from_dict_schema() + generate_from_schema()
                          + verify_integrity() + coherence_audit()
audit_dataset          →  misata.coherence_audit()
validate_domain        →  misata.validate_domain()
validate_yaml          →  json_schema() + validate_schema() + check_feasibility()
seed_database          →  misata.introspect + misata.db.seed_database()

Because the MCP server is bundled inside the misata package itself, not a separate distribution, the server and library are always in sync. Update Misata, the MCP server updates automatically.


Running standalone / debugging#

You don't normally need to run misata-mcp directly, your AI assistant launches it as a subprocess via stdio. For debugging:

Shell
misata-mcp
# Runs on stdio; send JSON-RPC requests on stdin, responses on stdout.

The easiest way to explore the tools interactively is the MCP Inspector:

Shell
npx @modelcontextprotocol/inspector misata-mcp

This opens a web UI where you can call each tool, inspect inputs and outputs, and iterate on prompts. Useful when writing system prompts that use Misata tools.


Discovery: Smithery#

Misata is listed on Smithery.ai, the MCP server discovery directory. If your AI assistant supports one-click MCP installation via Smithery, you can find Misata there and install it without editing config files manually.


Security note#

generate_dataset and generate_from_schema write CSV files to disk. By default they use a system temp directory that only the current user can read; if you pass a custom output_dir the agent writes only inside that directory. audit_dataset and validate_domain read the CSVs in a directory and write nothing.

seed_database is the only tool that writes to a database. It plans by default and inserts nothing until a second call with apply=true, and it will not overwrite a table that already contains rows without an explicit truncate or append. Point it only at a database you are willing to have written to.

Last updated Edit this page.md