OmniData MCP
OmniData MCP is a unified Model Context Protocol server for data querying, profiling, transformation, and visualization via DuckDB and PySpark, enforcing read-only, bounded, time-boxed, and audited access.
Health check: Verify server and DuckDB connection and guardrail configuration (
health_check).Data discovery: List datasets (
list_datasets), get schema (get_schema), and get exact row counts (get_row_count).Profiling: Generate per-column summary statistics including null %, distinct count, min/max, and mean/stddev/quartiles for numeric columns (
get_data_profile).SQL queries: Execute read-only SELECT/WITH/EXPLAIN/DESCRIBE/SHOW with automatic LIMIT injection, hard row caps, and timeouts (
run_sql_query).Visualization: Generate bar, line, or scatter charts from SQL query results as inline images or saved files (
generate_chart).Transformations: Run declarative PySpark pipelines (filter, select, withColumn, groupBy_agg, orderBy, distinct, limit) without arbitrary code execution (
execute_pyspark_job).
All operations are read-only and bounded, long-running processes have timeouts, and every tool call is logged locally for auditability; raw data never enters the LLM context.
Provides read-only SQL querying, schema inspection, row counting, and data profiling on DuckDB databases, with safety guardrails like statement allowlisting and row limits.
Generates bar, line, and scatter charts from SQL query results, saving them to disk and returning them as inline images.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@OmniData MCPprofile the sales table and create a chart of monthly revenue"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
OmniData MCP

A unified Model Context Protocol server for intelligent data engineering & analytics.
OmniData MCP lets LLM clients (Claude Desktop, Cursor, or any MCP-compatible client) query, profile, transform, and visualize data using DuckDB and PySpark, without raw data ever entering the model's context. Only metadata, bounded query results, and summarized outputs are ever returned to the LLM; every operation is logged locally for auditability.
Registry & Certification
OmniData MCP is officially published and indexed on the Glama MCP Registry, enabling seamless discovery and integration across supported LLM desktop environments.
Related MCP server: DuckDB MCP Server
Status: all planned phases complete
Phase | Scope | Tools added |
0 | Project scaffolding, MCP server skeleton |
|
1 | DuckDB query/profiling engine |
|
2 | Visualization |
|
3 | PySpark transformation engine |
|
4 | Hardening: audit logging, consistent error handling, docs | DONE |
See CHANGELOG.md for what changed in each phase, including two real
bugs found and fixed during development (an unhandled-exception error
path in Phase 4, and an unreliable Spark cancellation mechanism in
Phase 3) -- documented honestly rather than glossed over.
Quick start
uv sync
cp .env.example .env
uv run python scripts/seed_sample_data.py # creates sample sales/customers tables
uv run omnidata-db-server # sanity check: should hang silently (correct -- it's waiting on stdio)Connecting an MCP client
Claude Desktop, packaged/MSIX installs (most current Windows installs):
Raw claude_desktop_config.json editing does not work reliably on
this install type -- the file is app-managed and gets overwritten.
Use the included manifest.json:
Settings -> Extensions -> Advanced settings -> "Install Unpacked
Extension" -> select this project's root folder. Update
manifest.json's command/args paths first if your uv install or
project location differ from the defaults.
Claude Desktop (classic config), Cursor, or other MCP clients: Edit your client's MCP config directly:
{
"mcpServers": {
"omnidata-db": {
"command": "uv",
"args": ["run", "--directory", "/absolute/path/to/omnidata-mcp", "omnidata-db-server"]
}
}
}Either way, restart the client and ask it to call health_check to
confirm the connection.
Architecture
The original design called for two separate MCP servers -- a lightweight Database server and a heavier PySpark & Analytics server -- communicating with the client independently:
graph TD
Client["LLM Client<br/>(Claude Desktop / Cursor / custom)"]
Client -->|MCP Protocol, stdio| DB["Database MCP Server<br/>(DuckDB / PostgreSQL)"]
Client -->|MCP Protocol, stdio| Spark["PySpark & Analytics Server<br/>(PySpark session / Plotly)"]As built, this was deliberately consolidated into one server (db_server.py, one process, one manifest.json, one Claude Desktop extension) rather than split in two:
graph TD
Client["LLM Client<br/>(Claude Desktop / Cursor / custom)"]
Client -->|MCP Protocol, stdio| Server["OmniData MCP Server"]
subgraph Server["OmniData MCP Server (one process)"]
direction TB
DuckModule["DuckDB engine<br/>connection.py / query_safety.py"]
SparkModule["PySpark engine<br/>spark_session.py / spark_pipeline.py"]
ChartModule["Plotly charts<br/>charts.py"]
endRationale: for a single-user local tool, one process means one install/uninstall cycle in Claude Desktop, one shared config and audit log, and no need to coordinate two lifecycles for what's still a small number of tools (8). The internal module boundaries (connection.py/query_safety.py for DuckDB, spark_session.py/spark_pipeline.py for Spark, charts.py for visualization) still mirror the original two-server split logically -- splitting back into separate processes later, if concurrency or multi-user needs justify it, would mean moving files, not rewriting them.
Tool reference
Tool | Purpose | Key args |
| Verify the server + DuckDB connection are alive; reports current guardrail config | -- |
| List tables/views available to query | -- |
| Column names/types/nullability for one dataset |
|
| Exact row count for one dataset |
|
| Bounded, read-only SQL (SELECT/WITH/EXPLAIN/DESCRIBE/SHOW only) |
|
| Per-column stats (nulls, min/max, distinct count, quartiles) via DuckDB's |
|
| Bar/line/scatter chart from a query result -- saved to disk + returned inline |
|
| Declarative transformation pipeline (filter/select/withColumn/groupBy_agg/orderBy/distinct/limit) against a DuckDB table, run through Spark |
|
Security & governance model
The pitch for this project was governance-first: an LLM should be able to work with real data without raw rows ever landing in its context. Every tool honors that in practice, not just in the tagline:
Read-only by construction, not by convention.
run_sql_queryandgenerate_chartboth validate every statement against an allowlist (SELECT/WITH/EXPLAIN/DESCRIBE/SHOWonly, single-statement, no DDL/DML keywords anywhere in the text -- including inside comments or subqueries). Verified against 14 attack/edge cases during Phase 1 development.No arbitrary code execution.
execute_pyspark_jobtakes a declarative JSON pipeline from a fixed set of operations, not raw Python or PySpark code toexec(). Every step is validated before it touches Spark.Bounded by default. Every query auto-injects a
LIMITwhen one isn't specified, and results are hard-capped regardless of what's requested (max_row_limit,max_chart_rows,max_spark_input_rows).Timeouts on every long-running path, since neither DuckDB nor PySpark has this built in: a thread +
connection.interrupt()for DuckDB (confirmed via a genuinely slow query that got cancelled at ~10s and left the connection reusable afterward), a wall-clock timeout with best-effort cancellation for Spark (see the honest limitation noted below).Local audit trail. Every tool call is logged to
data/audit.logas JSON lines -- tool name, argument summary, duration, outcome. Query/operation metadata only, never raw dataset rows, consistent with the "raw data stays local" principle -- and the log itself never leaves your machine either.Consistent, structured errors. Every tool returns the same
{"error": "..."}shape on failure (enforced by the@auditeddecorator wrapping all 8 tools), rather than some failing cleanly and others surfacing raw unhandled exceptions.
Design decisions
Area | Decision |
Package manager |
|
MCP framework |
|
Query safety | Statement allowlist ( |
Sampling policy | Auto-inject |
PySpark pipelines | Declarative op allowlist, not arbitrary code execution; no join operation exposed |
PySpark session | Singleton |
Chart rendering | Plotly + |
Chart delivery | Saved to disk as a real file and returned as an inline MCP image -- the file is the reliable path given client display limitations (see below) |
Config |
|
Audit logging | JSON lines to |
Error handling | Every tool returns |
Project layout
omnidata-mcp/
|-- pyproject.toml
|-- manifest.json # Claude Desktop unpacked-extension manifest
|-- LICENSE
|-- CHANGELOG.md
|-- .env.example
|-- scripts/
| `-- seed_sample_data.py # creates sample sales/customers tables
|-- src/omnidata_mcp/
| |-- config.py # centralized settings (pydantic-settings)
| |-- connection.py # DuckDB connection + timeout enforcement
| |-- query_safety.py # read-only SQL allowlist validator
| |-- charts.py # Plotly chart building
| |-- spark_session.py # lazy SparkSession + timeout handling
| |-- spark_pipeline.py # declarative pipeline op validator/executor
| |-- audit.py # @audited: logging + error normalization
| `-- db_server.py # the 8 MCP tools
`-- data/ # local DuckDB file, charts/, audit.log (gitignored)Trying it out
Once uv sync and the seed script have run, ask your MCP client
things like:
"What datasets are available?" ->
list_datasets"What columns does the sales table have?" ->
get_schema"Profile the sales table" ->
get_data_profile"What's total revenue by product category?" ->
run_sql_query"Chart total revenue by product category" ->
generate_chart(bar)"Chart revenue over time by region as a line chart" ->
generate_chartwithseries_columnset to region"Use PySpark to compute average revenue per order, grouped by region, for orders over $50" ->
execute_pyspark_job(filter + groupBy_agg)
scripts/seed_sample_data.py populates data/omnidata.duckdb with two
tables: sales (500 rows, deliberately includes a few NULLs and one
outlier -- useful for exercising get_data_profile) and customers
(50 rows, referenced by sales.customer_id). Re-run it any time to
reset to a clean sample dataset.
Troubleshooting
PySpark tools fail to start on Windows, mentioning NativeIO$Windows
or UnsatisfiedLinkError. PySpark needs a JVM, and on Windows
specifically it also needs Hadoop's winutils.exe even in local mode
-- a well-known PySpark-on-Windows requirement unrelated to this
project.
Install Java 17 or 21 (JDK) from Eclipse Temurin; set
JAVA_HOMEand add%JAVA_HOME%\binto PATH.Download winutils.exe matching your Spark/Hadoop version from a trusted mirror (e.g. cdarlint/winutils), place it at
<hadoop_home>\bin\winutils.exe,setx HADOOP_HOME "<hadoop_home>", add%HADOOP_HOME%\binto PATH.Open a fresh terminal after either change -- PATH updates via
setxdon't apply retroactively to already-open windows.
Chart generation succeeds (per the tool result / audit log) but
nothing renders inline in the chat. This is a known Claude Desktop
limitation, not a bug in this project: it does not currently render
inline images from locally-installed unpacked extensions (as opposed
to marketplace-published ones). Every chart is always saved to
data/charts/<timestamp>_<id>.png regardless -- the tool's response
text includes that file's full path; open it directly.
Chart rendering fails demanding a Chrome install. Something bumped
kaleido past 1.0. Re-pin it: kaleido==0.2.1 in pyproject.toml,
then uv sync.
execute_pyspark_job keeps timing out on legitimately large inputs.
The timeout (spark_job_timeout_seconds, default 30s) has only
best-effort cancellation -- true in-JVM cancellation via Python threads
was tested during development and found unreliable in extreme cases (see
CHANGELOG.md, Phase 3). Try lowering max_spark_input_rows or adding
an earlier filter/limit step to your pipeline so there's less work
to do in the first place.
Any tool install/sync step fails with "no space left on device" /
WinError 112, even though your project's own .venv is on a drive
with plenty of room. Some Windows install paths can't be redirected
(Windows' native MSIX/AppX package installer always stages to
C:\Program Files\WindowsApps, and some tools' TEMP usage defaults
back to C:\Users\<you>\AppData\Local\Temp unless TEMP/TMP are
permanently redirected via setx). If you've hit this before,
re-check your TEMP/TMP/UV_CACHE_DIR environment variables are
still pointing where you expect, and separately confirm actual free
space on C: -- redirection isn't a substitute for real headroom on
installers that can't be redirected at all.
License
This project is licensed under the MIT License.
Author
MOSTAFA ABDELHAMED | Junior AI & DS Researcher | NVIDIA Gen AI Certified LinkedIn
Available Tools
8 toolsexecute_pyspark_jobA
Run a declarative PySpark transformation pipeline against a DuckDB dataset -- for heavier aggregations/transformations than run_sql_query is meant for. NOT arbitrary code execution: each step must be one of a fixed set of operations, validated before running.
Supported operations (each a dict with an "op" key): {"op": "filter", "condition": ""} e.g. {"op": "filter", "condition": "revenue > 100"} {"op": "select", "columns": ["a", "b"]} {"op": "withColumn", "name": "new_col", "expression": ""} e.g. {"op": "withColumn", "name": "margin", "expression": "revenue - cost"} {"op": "groupBy_agg", "group_by": ["a"], "aggregations": {"b": "sum"}} aggregations map column -> function; functions: sum, avg, mean, count, min, max, stddev, variance {"op": "orderBy", "columns": ["a"], "ascending": true} {"op": "distinct"} {"op": "limit", "n": 100}
Steps run in the order given. A final row cap is always applied to the output regardless of what the pipeline itself requests.
Args: source_dataset: Exact table/view name, as returned by list_datasets. operations: Ordered list of pipeline steps (see above). row_limit: Desired max rows returned (capped at the server's max_row_limit).
| Name | Required | Description | Default |
|---|---|---|---|
| row_limit | No | ||
| operations | Yes | ||
| source_dataset | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does so exceptionally. It discloses that steps run in order, an unconditional final row cap is applied, and row_limit is capped at server's max_row_limit. It also emphasizes the fixed op set and validation step, which prevents misuse.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but every sentence earns its place. It is front-loaded with the core purpose and limitation, then systematically covers operations, ordering, row cap, and arguments. No fluff or redundancy; the structure is logical and scannable.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a complex tool with no output schema, the description is remarkably complete. It details the full set of supported operations with examples, explains ordering and row cap behavior, and ties arguments to other tools (list_datasets). An agent has everything needed to invoke it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, but the description compensates fully. Each parameter is explained: source_dataset is an exact table name from list_datasets, operations is an ordered list with a detailed enumeration of supported ops and examples, and row_limit is a desired max rows capped by server. This adds meaning far beyond the bare schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states a specific verb (run), resource (declarative PySpark transformation pipeline against DuckDB dataset), and scope (heavier aggregations/transformations). It explicitly distinguishes itself from run_sql_query, and the phrase 'NOT arbitrary code execution' further clarifies its purpose.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit guidance on when to use it: 'for heavier aggregations/transformations than run_sql_query is meant for'. It also explains constraints: each step must be one of a fixed set of operations, validated before running. This frames appropriate usage and sets expectations.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
generate_chartA
Run a read-only SQL query and render the result as a chart image (bar, line, or scatter). Subject to the same read-only safety guardrails as run_sql_query.
Returns an inline chart image on success. On failure (bad SQL, missing column, empty result, oversized render), returns an error dict instead -- check for an "error" key if the result isn't an image. (No static return-type annotation here: the mcp SDK's output-schema generation can't handle Image inside a Union type.)
Args: sql: A single read-only SQL statement producing the data to chart. Aggregate/group the data yourself for cleaner charts (e.g. GROUP BY category). chart_type: "bar", "line", or "scatter". x_column: Column name (from the query result) for the x-axis. y_column: Column name (from the query result) for the y-axis. series_column: Optional column to split into multiple series/ traces (e.g. one line per region). title: Optional chart title. Defaults to "{y_column} by {x_column}".
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | ||
| title | No | ||
| x_column | Yes | ||
| y_column | Yes | ||
| chart_type | Yes | ||
| series_column | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are present, so the description fully carries the burden. It discloses read-only behavior, success returns an inline image, failure returns an error dict (with specific failure cases like bad SQL, missing column, empty result, oversized render), and explains the lack of a static return-type annotation due to SDK limitations. This is thorough and adds significant behavioral context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured: an overview sentence, a return/error contract, and a clear argument list. While it is long, the length is justified by the number of parameters and the need to explain error handling.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers input semantics, parameter defaults, failure modes, and return format. Since there is no output schema, it appropriately explains what the caller should expect on both success and failure. This is complete for a chart-generation tool with 6 parameters.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With schema description coverage at 0%, the description fully compensates by explaining each of the 6 parameters: sql with aggregation advice, chart_type enum, x_column, y_column, optional series_column, and title default. This adds meaning far beyond the bare parameter names in the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool runs a read-only SQL query and renders the result as a chart image, specifying chart types (bar, line, scatter). It distinguishes itself from sibling tools like run_sql_query by focusing on chart rendering rather than raw query results.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives useful usage context, such as read-only safety guardrails and advising the caller to aggregate/group data for cleaner charts. However, it does not explicitly state when to prefer this over alternatives like run_sql_query for non-chart needs, so it lacks an explicit exclusion or alternative recommendation.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_data_profileA
Generate summary statistics for every column in a dataset: type, null percentage, approximate distinct count, min/max, and (for numeric columns) mean/stddev/quartiles. Uses DuckDB's built-in SUMMARIZE, so it runs efficiently even on large tables.
Args: dataset: Exact table or view name, as returned by list_datasets.
| Name | Required | Description | Default |
|---|---|---|---|
| dataset | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the burden; it compensates by disclosing output fields, the approximate nature of distinct counts, and the DuckDB SUMMARIZE implementation with large-table efficiency. It does not explicitly state that the operation is read-only, but the 'get' verb and summary-statistics framing strongly imply no mutation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact: one purpose sentence, one implementation/performance sentence, and a one-item Args entry. Every sentence earns its place, and the most important information is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With only one parameter and no output schema, the description gives essential output details and input provenance, making selection and invocation feasible. It lacks a precise return-shape description and explicit alternative guidance, but the listed statistics and sibling tool names provide sufficient context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has only 'dataset' with a title and no description (0% coverage), while the description's Args section adds actionable semantics: 'Exact table or view name, as returned by list_datasets.' This fully clarifies the value format and provides the source for valid inputs.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific action ('Generate summary statistics') and explicitly enumerates the output fields (type, null percentage, approximate distinct count, min/max, mean/stddev/quartiles). This clearly distinguishes it from siblings like get_schema and get_row_count by covering full-column profiling rather than just schema or row count.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description supplies a concrete prerequisite ('dataset: Exact table or view name, as returned by list_datasets') and notes performance suitability for large tables via DuckDB's SUMMARIZE. It stops short of explicitly naming sibling alternatives or stating when not to use this tool, but the intended context is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_row_countA
Get the exact row count for one dataset. Use this before run_sql_query on a large table to know how much data you're sampling from with a LIMIT.
Args: dataset: Exact table or view name, as returned by list_datasets.
| Name | Required | Description | Default |
|---|---|---|---|
| dataset | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the transparency burden. It clarifies that the tool returns an exact count for a single dataset, but it does not disclose whether this is a full scan or metadata lookup, nor any performance or failure characteristics.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact: a purpose statement, a usage tip, and an Args section. Every sentence adds value, and the structure is easy to parse.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple tool with one parameter and no output schema, the description covers purpose, context, and parameter semantics. It omits the exact return type, but given the name 'get_row_count' this is likely obvious; however, it could explicitly state that it returns a non-negative integer.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Despite 0% schema coverage, the description explains the 'dataset' parameter as 'Exact table or view name, as returned by list_datasets,' giving agents a precise source and format. This adds substantial meaning to the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb and object — 'Get the exact row count for one dataset' — which clearly defines its function. It also hints at its targeted use case, distinguishing it from siblings like run_sql_query and list_datasets.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly instructs to use before run_sql_query on large tables to understand sampling scope. This provides clear contextual guidance and implies when not to use it (as a substitute for querying).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_schemaA
Get the column names, types, and nullability for one dataset (table or view). Use list_datasets first to get valid names.
Args: dataset: Exact table or view name, as returned by list_datasets.
| Name | Required | Description | Default |
|---|---|---|---|
| dataset | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the burden of disclosing behavior. It explicitly states the output content (column names, types, nullability) and that the input must be an exact name from list_datasets. The read-only nature is implied by 'Get', and no side effects or permission requirements are mentioned, but for a metadata read this is acceptable.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is extremely concise: two sentences plus a brief Args section. Every sentence contributes value, and the front-loaded purpose statement ensures immediate understanding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple one-parameter read-only tool, the description provides the essential information: purpose, input requirement, and prerequisite. No output schema is needed because the return type is described as column names, types, and nullability, which fully specifies the result expected.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema provides only a string parameter with no description. The tool description fully compensates by explaining that 'dataset' must be the exact table or view name returned from list_datasets, and that it is required. This gives the agent precise guidance on how to populate the parameter.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's function: retrieving column names, types, and nullability for a single dataset. It distinguishes this from sibling tools like list_datasets (which lists available datasets) and get_row_count (which counts rows), and specifies both tables and views are covered.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The instruction 'Use list_datasets first to get valid names' provides a clear prerequisite and sequence of use. It does not explicitly list when not to use the tool, but the context of sibling tools makes the alternative use cases clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
health_checkA
Verify the OmniData database server is running, can reach DuckDB, and report its current guardrail configuration (row limits, timeout, DB path).
Use this first to confirm the MCP connection is alive.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It discloses what the tool does (check if server is running, can reach DuckDB, report guardrail config) and implies read-only behavior through 'Verify' and 'report'. It does not explicitly state 'does not modify data', but the health-check nature and reported fields are transparent enough. Could add explicit read-only confirmation, but the description is informative.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences: the first gives specific technical details, the second gives direct usage guidance. Every sentence carries weight; there is no fluff or redundancy. It is front-loaded with the most important purpose first.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's low complexity (no parameters, no output schema, simple health check), the description is complete. It explains what is verified (server, DuckDB connection), what is reported (guardrails: row limits, timeout, DB path), and when to use it. No additional context is necessary for an agent to select and invoke this tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, and the schema already covers this completely (100% coverage with an empty properties object). There is nothing for the description to add about parameters, so a baseline of 4 is appropriate. The description mentions the report fields, which indirectly clarifies what the tool returns.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb 'Verify' and the specific resource (OmniData database server, DuckDB reachability, guardrail configuration). It is highly specific and distinguishes itself from sibling tools like list_datasets or run_sql_query by focusing on connection health and configuration status.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear context: 'Use this first to confirm the MCP connection is alive.' This gives explicit usage timing (before other tools). It does not explicitly exclude alternatives or name when-not-to-use, but the instruction 'first' is strong guidance. Missing explicit alternative names prevents a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_datasetsA
List every table and view available in the database, with its type and column count. Call this first when exploring an unknown database -- run_sql_query needs real table names to work with.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the responsibility of behavioral disclosure. It clearly states the tool lists tables and views with type and column count, which is the primary behavior. However, it does not explicitly mention that it is read-only or describe any permissions or side effects, though the verb 'List' implies a non-mutating operation. This is a minor gap, making the transparency adequate but not fully explicit.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences long, with the first sentence stating the exact purpose and return fields, and the second providing context on when to call it. There is no redundant information; every phrase adds value.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity (no parameters, no output schema), the description covers the essential information: what is listed, what is returned, and when to use it. It also provides a rationale that ties into the sibling tool run_sql_query. Minor gaps like handling of empty databases or system schemas are not critical for this simple tool, so it is fairly complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, and the input schema is empty. Per guidelines, the baseline for 0 parameters is 4. The description adds no parameter-specific semantics because none exist, but it also doesn't need to. Thus, it meets the baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses the specific verb 'List' with a clear resource ('every table and view available in the database') and explicitly states the output fields ('type and column count'). It distinguishes itself from sibling tools by positioning this as a discovery tool, and the note that run_sql_query needs real table names reinforces its role.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit usage timing: 'Call this first when exploring an unknown database' and explains why, noting that run_sql_query depends on these names. This is clear guidance for when to use it. It doesn't explicitly say when not to use it, but for a listing tool, the directive to call it first is sufficient. It also implies an alternative (run_sql_query) but frames it as a dependent tool rather than a direct alternative.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
run_sql_queryA
Execute a read-only SQL query and return structured results.
Safety guardrails (see README "Design decisions"):
Only SELECT / WITH / EXPLAIN / DESCRIBE / SHOW statements are permitted; DDL/DML and system commands are rejected.
Only one statement per call.
If the query has no LIMIT clause, one is auto-injected using
row_limit(or the server default). Results are always hard-capped at the server's max_row_limit, regardless of what you request, to keep responses bounded.The query is cancelled if it runs longer than the server's configured timeout.
Use get_row_count first if you need to know the true size of a table beyond what this capped result shows.
Args: sql: A single read-only SQL statement. row_limit: Desired max rows (capped at the server's max_row_limit). Ignored if your query already has LIMIT.
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | ||
| row_limit | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries full burden and does an excellent job: it discloses read-only enforcement, single-statement restriction, automatic LIMIT injection, server-side row cap, and query timeout cancellation. This is far beyond minimal.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-organized with a short intro, bullet-pointed guardrails, and a compact Args section. Every sentence adds relevant information and there is no wasted text.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description thoroughly covers safety guardrails, limits, and the row-count caveat. However, it does not describe the exact shape of the 'structured results' return value, and with no output schema, a bit more detail about the return format would make it fully complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, but the description fully compensates by explaining each parameter: sql is a single read-only statement, and row_limit is a desired cap that may be ignored if the query already has LIMIT. This adds meaning the schema lacks entirely.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states it executes a read-only SQL query and returns structured results, with explicit allowed statement types (SELECT/WITH/EXPLAIN/DESCRIBE/SHOW). It is easily distinguished from sibling tools like get_row_count, get_schema, and list_datasets.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly recommends using get_row_count when true table size is needed, providing a clear alternative. However, it does not explicitly contrast with other data exploration tools like get_data_profile or generate_chart, so the guidance is good but not exhaustive.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
TDQS
Each tool targets a distinct concern: health check, dataset discovery, schema introspection, row counting, SQL queries, profiling, charting, and declarative transformations. Even run_sql_query vs generate_chart are clearly differentiated by output type (structured data vs. image).
The naming convention is predominantly verb_noun (list_datasets, get_schema, run_sql_query, generate_chart, execute_pyspark_job). The sole outlier is health_check, which follows noun_verb order and is not consistent with the others.
With 8 tools, the server is well-scoped for a database analytics MCP. Each tool covers a genuine need without redundancy or bloat, staying within the ideal 3-15 range.
The tool surface covers the full analytical lifecycle: connect/health, explore structure, query, profile, chart, and transform. While data modification is intentionally absent, all read-only analysis needs are addressed without obvious gaps.
Maintenance
Related MCP Connectors
The BigQuery remote MCP server is a fully managed service that uses the Model Context Protocol to connect AI applications and LLMs to BigQuery data sources. It provides secure, standardized tools for AI agents to list datasets and tables, retrieve schemas, generate and execute SQL queries through natural language, and analyze data—enabling direct access to enterprise analytics data without requiring manual SQL coding.
The grounded data layer for any LLM: governed SQL, metrics, lineage and catalog over your data.
Query, join, profile, clean and convert CSV/JSON/Parquet with server-side DuckDB over MCP.
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yo…
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceAn enhanced Model Context Protocol server that enables LLMs to inspect database schemas with rich metadata and execute read-only SQL queries with safety checks.26925MIT
- AlicenseNot gradedqualityDmaintenanceA Model Context Protocol server implementation that connects AI assistants to DuckDB, enabling them to query and analyze data from various sources including CSV, Parquet, JSON, and cloud storage through SQL.18MIT
- AlicenseAqualityBmaintenanceA Model Context Protocol server that enables large language models to access database metadata and perform cross-engine data querying across diverse database ecosystems.1652Apache 2.0
- FlicenseNot gradedqualityDmaintenanceModel Context Protocol (MCP) server that gives AI assistants a safe, correct data-analyst capability over business metrics - without raw SQL improvisation.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/MostafaAI10/OmniData-MCP'
If you have feedback or need assistance with the MCP directory API, please join our Discord server