Skip to main content
Glama
dsr-cyber

bizdata-mcp

by dsr-cyber

mcp-business-data

An MCP server that lets Claude answer questions about a small business's own data, plus a short command-line agent that does the same thing through the Claude API.

The data here belongs to a made-up outdoor gear shop, Harbor & Pine Supply: about 3,000 customers, 46 products, 4,300 orders and 330 refunds over 18 months (April 2025 to September 2026), generated with seasonality, category growth, a few loyal repeat buyers, one tent with a zipper problem, and some items about to run out. I built it to show the pattern I use with clients: give the model a few well-defined business tools, a read-only SQL escape hatch with real guard rails, and a log of everything it ran.

Swap the SQLite file for your own database and most of this carries over. See Adapting it to your own database.

Quick start

Needs Python 3.11 or newer.

git clone <this repo> mcp-business-data
cd mcp-business-data
python -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install -r requirements.txt
python scripts/make_sample_db.py   # writes data/harbor_pine.db (seeded, same data every run)
pytest

Then connect it to Claude Desktop or Claude Code (below) and ask something like "which products should I reorder this week?"

Related MCP server: mcp-analytics-server

The tools

Tool

What it answers

sales_summary(start, end, group_by)

Orders, units, revenue, refunds and net revenue by day, week, month, quarter, category, product or channel, with a totals row.

top_customers(n, start, end)

Best customers by net revenue after refunds.

refund_rate(by, start, end)

Refunded dollars as a share of revenue, by category, product, month or channel.

inventory_alerts(min_days_of_cover)

Active products that are out of stock, at or below their reorder point, or under N days of cover at the last 30 days' sales rate.

describe_schema()

Tables, columns, row counts, foreign keys, and notes such as "revenue lives on order_items, not orders".

run_sql(query)

One read-only SELECT for anything the other tools don't cover. Returns columns, rows, row_count and truncated.

The schema is also published as an MCP resource, schema://tables, in Markdown.

The business tools exist because "revenue" is ambiguous. Does it include shipping? Cancelled orders? Are refunds subtracted, and by order date or refund date? Left to write its own SQL, a model will pick an answer, and it won't always pick the same one. The fixed tools encode the store's definitions once (completed orders only, shipping excluded, refunds attributed to the original order's date), and the schema notes repeat them for when the model does fall back to run_sql. The business tools only run SQL written ahead of time. Values go in as parameters, and group_by picks from a fixed dict of SQL expressions rather than being pasted into the query.

Every call, from the server or from ask.py, is appended to logs/audit.jsonl:

{"ts": "2026-10-06T15:22:57.245+00:00", "source": "mcp", "tool": "sales_summary", "args": {"start": "2026-04-01", "end": "2026-06-30", "group_by": "category"}, "rows": 6, "duration_ms": 8.06, "ok": true}
{"ts": "2026-10-06T15:22:57.358+00:00", "source": "mcp", "tool": "run_sql", "args": {"query": "DROP TABLE orders"}, "rows": null, "duration_ms": 0.03, "ok": false, "error": "QueryError: only SELECT (or WITH ... SELECT) queries are allowed"}

How the read-only guard works

run_sql runs SQL that a model wrote, so it goes through three independent layers. Any one of them would stop a write on its own.

  1. Text check (src/bizdata/sqlguard.py). Comments are stripped and string literals blanked, then the query must be a single statement starting with SELECT or WITH, with no INSERT, UPDATE, DELETE, DROP, PRAGMA, ATTACH and so on outside of quotes. This is the weakest layer. Its job is to give the model a clear error message it can act on.

  2. Read-only connection. The file is opened with SQLite's mode=ro URI flag and PRAGMA query_only = ON. SQLite itself refuses to write.

  3. SQLite authorizer. SQLite calls back for every operation in the compiled statement, and the callback allows only SELECT, column reads, recursive CTEs, and function calls not on a short block list (load_extension, zeroblob, randomblob and a few others). That covers anything the text check might have misparsed, because it judges what SQLite actually compiled rather than what the text looked like.

On top of that:

  • Results stop at 200 rows (BIZDATA_MAX_ROWS). The response says "truncated": true so the model knows it's looking at part of the answer.

  • A progress handler cancels a query after 5 seconds (BIZDATA_TIMEOUT_S). The test suite checks this with an endless recursive CTE.

  • Strings and blobs are capped at 1 MB, and attaching other databases is turned off.

  • Columns listed in BIZDATA_HIDDEN_COLUMNS (default customers.email) read as NULL through run_sql, including in WHERE clauses, so WHERE email LIKE 'a%' can't be used to guess them. describe_schema tells the model the column is hidden.

The tests in tests/test_run_sql.py call the executor directly with the text check skipped, to show layers 2 and 3 hold by themselves.

What it doesn't protect against

  • Reading. Anything not hidden can be read, by design. If a table shouldn't be visible to the model, it shouldn't be in this database file, or its columns should be hidden.

  • Where the data goes. Rows that tools return are sent to whichever model is using them. With Claude Desktop, Claude Code or ask.py, that means Anthropic's API. Check that this is acceptable for your data before connecting it.

  • Prompt injection through data. If a customer types instructions into a free-text field and a tool returns that text, the model reads it. With read-only tools the damage is limited to a misleading answer, but it's not zero.

  • Load. A 5-second query can still be an expensive one. On a busy production database, point this at a replica.

  • The HTTP transport has no authentication. It binds to 127.0.0.1 by default. Don't expose it to a network without putting authentication in front of it.

Sample: "Which product category grew fastest last quarter?"

I haven't run ask.py against the API for this README, so there is no model transcript here. What follows is real output from a local run: the server started over stdio, and these are the tool calls a model would make for this question (output trimmed to the fields that matter).

>>> sales_summary {"start": "2026-04-01", "end": "2026-06-30", "group_by": "category"}
>>> sales_summary {"start": "2026-07-01", "end": "2026-09-30", "group_by": "category"}

category       Q2 net       Q3 net   change
Camping     64,706.55    76,523.35   +18.3%
Water       41,345.00    48,856.00   +18.2%
Hiking      35,804.25    35,682.05    -0.3%
Apparel     16,906.20    19,337.60   +14.4%
Climbing    16,301.05    14,960.30    -8.2%
Accessories  8,666.65     9,309.00    +7.4%

Quarter over quarter, Camping edges out Water. That's mostly the season, though. Run the same call for Q3 2025 and the picture changes: Climbing is up 49% on the same quarter last year, Camping 38%, Accessories 35%, and Water is flat. A good answer mentions both, and the system prompt in ask.py tells the model to state which dates it compared.

A few more calls from the same run:

>>> inventory_alerts {}
{"sku": "HIK-008", "name": "Trail Map Case", "stock_on_hand": 0, "reorder_point": 5, "units_last_30d": 9, "days_of_cover": 0.0, "status": "out of stock"}
{"sku": "ACC-003", "name": "Multi-Tool", "stock_on_hand": 1, "reorder_point": 6, "units_last_30d": 11, "days_of_cover": 2.7, "status": "at or below reorder point"}
...  as_of 2026-09-30

>>> refund_rate {"by": "product", "start": "2026-01-01", "end": "2026-09-30"}
{"product": "Cedar Flat 4P Family Tent", "units_sold": 138, "units_refunded": 25, "revenue": 47080.1, "refunded": 8602.85, "refund_rate_pct": 18.27}
{"product": "Sun Hoodie", "units_sold": 24, "units_refunded": 4, "revenue": 1269.0, "refunded": 216.0, "refund_rate_pct": 17.02}
...

>>> run_sql {"query": "SELECT email FROM customers LIMIT 2"}
{"columns": ["email"], "rows": [[null], [null]], "row_count": 2, "truncated": false}

>>> run_sql {"query": "DROP TABLE orders"}
Error executing tool run_sql: only SELECT (or WITH ... SELECT) queries are allowed

The family tent is the planted quality problem. The hoodie is noise from 24 units, which is why units_sold sits next to every rate.

Connecting it to Claude

The server runs over stdio by default. Use absolute paths, since neither client starts it from this folder.

Claude Code

claude mcp add --env BIZDATA_DB=/path/to/mcp-business-data/data/harbor_pine.db --transport stdio harbor-pine-data \
  -- /path/to/mcp-business-data/.venv/bin/bizdata-mcp

On Windows the command is C:\path\to\mcp-business-data\.venv\Scripts\bizdata-mcp.exe. Add --scope project to write it to a .mcp.json you can commit for your team, or --scope user to have it in every project.

Claude Desktop

Add this to claude_desktop_config.json (Settings > Developer > Edit Config), then restart the app.

{
  "mcpServers": {
    "harbor-pine-data": {
      "command": "/path/to/mcp-business-data/.venv/bin/bizdata-mcp",
      "env": {
        "BIZDATA_DB": "/path/to/mcp-business-data/data/harbor_pine.db"
      }
    }
  }
}

Over HTTP

Streamable HTTP, for a server that other machines reach through an authenticating proxy:

bizdata-mcp --http --host 127.0.0.1 --port 8000
claude mcp add --transport http harbor-pine-data http://127.0.0.1:8000/mcp

Settings

All optional, as environment variables:

Variable

Default

BIZDATA_DB

data/harbor_pine.db in this repo

BIZDATA_AUDIT_LOG

logs/audit.jsonl in this repo

BIZDATA_MAX_ROWS

200

BIZDATA_TIMEOUT_S

5

BIZDATA_HIDDEN_COLUMNS

customers.email (comma-separated table.column; set it empty to hide nothing)

ask.py

A command-line agent for when you want answers without a chat app, or want to see the tool-use loop itself.

export ANTHROPIC_API_KEY=sk-ant-...      # or put it in .env (see .env.example)
python ask.py "Which product category grew fastest last quarter?"

It calls the same Store methods the server wraps, in-process, with no MCP in between. The tool names, descriptions and input schemas are read from the MCP server definition, so there's one place to edit them, and a test fails if a tool and its method ever disagree. The loop is run_agent in src/bizdata/agent.py, about 60 lines: send the question with the tools, run whatever tools come back, return all the results in one message, repeat until the model answers or 12 rounds pass. A bad tool call goes back to the model as an error so it can correct itself.

It uses claude-opus-5-5 at medium effort. Change them with --model, BIZDATA_MODEL or BIZDATA_EFFORT (set BIZDATA_EFFORT= empty for older models that don't accept the effort setting). For current models it also turns on the API's server-side refusal fallback, so a request the main model declines is retried on a fallback model instead of coming back empty. Without a key it prints setup instructions and exits.

Adapting it to your own database

The SQLite-specific parts are small: Store.connect and Store._execute_guarded (connection, authorizer, timeout), describe_schema (uses PRAGMA table_info), the date functions inside reports.py, and the DDL in schema.py. Rewrite TABLE_NOTES, COLUMN_NOTES and CONVENTIONS in schema.py for your tables. That file has more effect on answer quality than any of the code.

For a server database, don't rely on the text check. Let the database enforce read-only access.

PostgreSQL

  • Connect as a role that only has SELECT on the tables you want exposed. For hidden columns, use column-level grants (GRANT SELECT (id, name, state) ON customers TO ai_reader) or a view.

  • Set the limits on the role, so they hold however the connection is opened: ALTER ROLE ai_reader SET default_transaction_read_only = on; ALTER ROLE ai_reader SET statement_timeout = '5s';

  • With psycopg 3, also set conn.read_only = True, and use cursor.fetchmany(max_rows + 1) for the row cap, as here.

  • describe_schema reads information_schema.columns. In the reports, replace strftime('%Y-%m', ...) with to_char(date_trunc('month', ...), 'YYYY-MM'), and the :name parameters with %(name)s.

  • Point it at a read replica if one exists.

MySQL

  • A user with SELECT only, plus column-level grants or views for anything sensitive.

  • Per session: SET SESSION TRANSACTION READ ONLY and SET SESSION max_execution_time = 5000 (milliseconds; it applies to SELECT statements).

  • Parameters become %(name)s with PyMySQL or mysql-connector, and strftime becomes DATE_FORMAT.

Keep the business tools close to the questions the owner actually asks. Five tools that match how they talk about the business beat thirty generic ones.

Limitations

  • The sample data is generated. The numbers illustrate the tools and say nothing about any real store.

  • inventory_alerts measures sales velocity up to the most recent order in the data rather than today's date, so it works on a static sample. On a live database those are the same thing.

  • One database, one set of credentials, no per-user permissions. Everyone connected to the server sees the same data.

  • run_sql returns at most 200 rows. The model is told when it hit the cap, but it can still reason from a partial result if it ignores the flag.

  • The MCP SDK is pinned to the 1.x line (mcp>=1.26,<2). Version 2 renames FastMCP to MCPServer and moves transport settings onto run(). I haven't ported to it yet.

  • ask.py is tested against a fake client and against the real SDK talking to a mocked HTTP endpoint, not against the live API.

Layout

src/bizdata/
  server.py     MCP server: tool definitions and descriptions, schema resource
  store.py      read-only connection, authorizer, run_sql, describe_schema, audited methods
  sqlguard.py   text check for model-written SQL
  reports.py    fixed SQL for the business tools
  schema.py     DDL plus the notes the model reads
  audit.py      JSONL audit log
  agent.py      tool-use loop used by ask.py
scripts/make_sample_db.py
ask.py
tests/

MIT licensed.

Available Tools

6 tools
describe_schemaA

List every table with its columns, row counts, foreign keys, and notes on how to use them.

Read the conventions section before writing SQL: it defines revenue, refunds and date formats.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description carries the full behavioral burden; it discloses the concrete contents returned (columns, row counts, FKs, usage notes) and flags an auxiliary conventions section defining revenue, refunds, and date formats. For an argument-free read-only metadata tool there is little further behavior to disclose, though it never states the operation is safe/non-mutating.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight sentences with no filler; the inventory of returned content is front-loaded and the workflow directive follows. Every clause earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be re-explained, and the description still supplies the key non-obvious fact: the conventions section defines revenue, refunds, and date semantics. What is missing is any statement about ordering relative to the sibling query tools or freshness of the listed schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so per the rubric the baseline is 4. There is nothing parameter-related for the description to clarify or compensate for.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('List') and resource ('every table') and enumerates the payload: columns, row counts, foreign keys, and usage notes. This clearly separates it from the query-executing siblings like run_sql, sales_summary, or refund_rate without needing to open any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly directs the agent to read the conventions section before writing SQL, which establishes the tool as the prerequisite discovery step. It does not, however, name a specific alternative or state when not to use it (e.g., 'skip this if you already know the schema'), so it falls short of full routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

inventory_alertsB

Active products that are out of stock, at or below their reorder point, or running low.

Days of cover = stock on hand / average daily units sold over the last 30 days, measured up to the most recent order in the data. Products with no recent sales show no cover figure.

ParametersJSON Schema
NameRequiredDescriptionDefault
min_days_of_coverNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

B3.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It usefully discloses the exact computation of days of cover and the edge-case behavior that products with no recent sales show no cover figure. However, it never states that this is a read-only operation, says nothing about ordering, limits, or whether inactive products are excluded beyond the single word 'Active'.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with the qualifying conditions followed by the metric definition. Every sentence earns its place, though the metric definition could be tightened slightly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need no explanation, and the tool is a simple single-parameter read. The qualifying criteria and metric semantics are covered; only the parameter's filter behavior and result ordering are left unstated.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% — min_days_of_cover is documented only as 'integer, default 14'. The description compensates by defining what 'days of cover' means and how it is computed, which is the parameter's unit, but it never explains how the threshold is applied (filter direction, inclusivity), leaving the parameter's effect on results ambiguous.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names the resource and the precise conditions that qualify a product (out of stock, at/below reorder point, running low), which is far more specific than the bare tool name. It lacks an explicit verb (e.g., 'Returns/Lists'), but no sibling (run_sql, sales_summary, top_customers, refund_rate) overlaps this domain, so differentiation is not a concern.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no statement of when to reach for this tool versus alternatives such as run_sql or sales_summary, and no prerequisites or exclusions are given. The reader must infer usage entirely from the returned-content description.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

refund_rateA

Refunded dollars as a percentage of revenue, plus units sold and refunded, grouped by by.

Dates filter on when the order was placed. Small groups can show extreme rates; check units_sold before drawing conclusions.

ParametersJSON Schema
NameRequiredDescriptionDefault
byNocategory
endNo
startNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral burden and does disclose non-obvious traits: that start/end filter on order-placement date rather than refund date, and that low-volume groups produce statistically unreliable rates. It says nothing about permissions, cost, or row limits, but the read-only nature of an aggregate metric query is self-evident from the text.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences, each earning its place: metric definition first, then filter semantics, then the interpretation caveat. Zero filler and correctly front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The presence of an output schema means return values need no explanation, and the description covers what is computed, how grouping works, and how dates are applied. The remaining gap is the undocumented date/timestamp format for start and end, which an agent must guess.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It explains the semantics of `by` (grouping dimension) and clarifies that start/end filter on order placement date, which is real added meaning, but it never states the accepted date string format or defaults, leaving half the parameters underspecified.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description defines the exact computation ('refunded dollars as a percentage of revenue, plus units sold and refunded, grouped by `by`'), which is a specific metric that is immediately distinguishable from sales_summary or top_customers. An agent knows precisely what number this tool returns without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied by the metric definition, and the description adds a genuine analytical caveat ('small groups can show extreme rates; check units_sold before drawing conclusions'). However, it never names an alternative or states when to prefer another sibling, and no when-not conditions are given.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

run_sqlA

Run one read-only SQLite SELECT (or WITH ... SELECT) and return columns, rows and a truncated flag.

Results are capped (200 rows by default) and queries are stopped after a few seconds. Aggregate in SQL rather than pulling raw rows. Call describe_schema first if you haven't seen the tables.

ParametersJSON Schema
NameRequiredDescriptionDefault
queryYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it declares read-only scope, a default 200-row cap, a multi-second timeout, and a truncated flag. Gaps remain on error behavior and whether the cap is configurable, so it is not exhaustive.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with the scope and return shape, then limits, then the prerequisite. Every sentence carries actionable information with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return formatting need not be spelled out, and the description still concisely names the return pieces. Combined with read-only scope, limits, and the describe_schema prerequisite, an agent has nearly everything needed to call it safely.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% and there is one undocumented parameter, but the description constrains it meaningfully: the query must be a read-only SELECT or WITH ... SELECT. That syntax constraint adds real meaning beyond the bare "Query" string in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (run) and resource (read-only SQLite SELECT or WITH ... SELECT) and names the exact return shape (columns, rows, truncated flag). An agent can distinguish this generic query tool from siblings like describe_schema and the canned sales_summary/refund_rate tools without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit prerequisite ("Call describe_schema first if you haven't seen the tables") and a workflow preference ("Aggregate in SQL rather than pulling raw rows"). It stops short of naming when to prefer the prebuilt siblings (sales_summary, top_customers) over raw SQL, so no full when/when-not routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sales_summaryA

Orders, units, revenue, refunds and net revenue, grouped by time period or dimension.

start/end are inclusive YYYY-MM-DD dates; leave either out for no bound. Only completed orders count. Refunds are attributed to the original order's date. Weeks start on Monday. Includes a totals row with average order value.

ParametersJSON Schema
NameRequiredDescriptionDefault
endNo
startNo
group_byNomonth

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden and does it well: it discloses that only completed orders count, refunds are attributed to the original order's date, weeks start Monday, and a totals row with average order value is appended. These are non-obvious business rules an agent could not infer from the schema; only auth/rate-limit context is absent, which is minor for a read tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The opening sentence is front-loaded with what the tool returns, and each following sentence adds a distinct rule (date bounds, completed-only, refund attribution, week start, totals row). Dense with zero filler, though the terse fragment style slightly reduces readability.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return-shape explanation is not required, yet the description still adds the totals-row/AOV detail. Business logic is thorough; only the undocumented group_by default and any permission requirements are missing, which is a small residual gap for a 3-param aggregation tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must compensate, and it largely does: start/end are defined as inclusive YYYY-MM-DD with 'leave either out for no bound,' which is genuinely useful. group_by is only indirectly covered ('time period or dimension' maps onto the enum values), and the default of month is never stated, leaving one small gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names the resource (sales metrics: orders, units, revenue, refunds, net revenue) and the aggregation axis (time period or dimension), so an agent knows exactly what it computes. It does not explicitly contrast itself with siblings like run_sql or top_customers, but the scope is specific enough to route on.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied: the metrics list and grouping options suggest 'use this for aggregated sales reporting,' but there is no explicit when-to-use, no exclusions, and no mention of the obvious alternative (run_sql) present in the sibling list. The date-bound behavior hints at scope but is not framed as guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

top_customersA

The n customers with the highest net revenue (after refunds) in an optional date range.

n is 1-100. Returns customer id, name, state, order count, revenue, refunds, and first/last order time.

ParametersJSON Schema
NameRequiredDescriptionDefault
nNo
endNo
startNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations present, the description carries the full burden. It does add real behavioral context: the metric is net of refunds, n is bounded to 1-100, and the returned fields are enumerated. However, it says nothing about ordering/ties, date semantics, or whether the result is truncated, so it is only partially complete for a tool with zero annotation coverage.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with the identifying clause and followed by the constraint and return summary. Every sentence earns its place with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so the return-field listing is belt-and-braces rather than a gap. The remaining omissions (date format, tie-breaking, empty-result behavior) are minor for a read-only analytical tool, making the definition adequate for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It usefully constrains 'n is 1-100' (the schema gives only a default of 10 with no bounds) and signals that the date range is optional, but it never states the expected format for start/end or whether the bounds are inclusive, leaving two of three parameters underspecified.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb+resource and a precise ranking metric: 'the n customers with the highest net revenue (after refunds)', which sharply distinguishes it from a generic query tool. It does not explicitly contrast itself with siblings like sales_summary or refund_rate, so it stops short of a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied by 'in an optional date range,' which tells the agent the tool is scoped to a time window, but there is no explicit when-to-use/when-not guidance or naming of the alternatives (run_sql, sales_summary) that overlap with this result set.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 6 tool updatesv0.1.0
    • First observeddescribe_schema
    • First observedinventory_alerts
    • First observedrefund_rate
    • First observedrun_sql
    • First observedsales_summary
    • First observedtop_customers

TDQS

A3.7/5.0

Scored across 6 tools

Disambiguation4/5

Each analytical tool targets a distinct question (sales over time, top customers, refund rate, inventory health), and describe_schema is clearly a discovery step. The only blur is run_sql, which can technically reproduce any of the specialized tools, but descriptions steer agents toward the purpose-built ones.

Naming Consistency3/5

All names are snake_case, but the conventions are mixed: run_sql and describe_schema use verb_noun while sales_summary, top_customers, refund_rate and inventory_alerts use noun-style labels. Still readable and predictable enough to navigate.

Tool Count5/5

Six tools is well-scoped for a focused business-analytics server: schema discovery, a SQL escape hatch, and four targeted analytics. Each tool earns its place with no redundancy.

Completeness4/5

The surface covers revenue, refunds, customers, and inventory, plus describe_schema and a raw SQL escape hatch that fills most gaps. Minor gaps exist for product-level performance or period-over-period comparisons, but agents can work around them via run_sql.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables AI to query a business database for customers, orders, and revenue using natural language through safe, well-defined tools.
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables LLMs to interact with a SQLite e-commerce database via safe, typed MCP tools with read-only guards and auth-gated mutations, plus a Claude agent for answering business questions.
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Provides structured, read-mostly access to small-business back-office data including customers, invoices, and account notes, allowing Claude to query overdue invoices, revenue summaries, and more.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants like Claude to query, analyze, and summarize local SQLite e-commerce databases using natural language through 11 tools, schema resources, and pre-built analytical prompts.
    -