bizdata-mcp
Connects to a SQLite database containing business data, supporting schema discovery, business-specific reporting tools, and read-only SELECT queries with safety guardrails.
Click on "Deploy 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., "@bizdata-mcpwhich products should I reorder this week?"
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.
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)
pytestThen 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 |
| Orders, units, revenue, refunds and net revenue by day, week, month, quarter, category, product or channel, with a totals row. |
| Best customers by net revenue after refunds. |
| Refunded dollars as a share of revenue, by category, product, month or channel. |
| 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. |
| Tables, columns, row counts, foreign keys, and notes such as "revenue lives on order_items, not orders". |
| One read-only SELECT for anything the other tools don't cover. Returns |
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.
Text check (
src/bizdata/sqlguard.py). Comments are stripped and string literals blanked, then the query must be a single statement starting withSELECTorWITH, with noINSERT,UPDATE,DELETE,DROP,PRAGMA,ATTACHand 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.Read-only connection. The file is opened with SQLite's
mode=roURI flag andPRAGMA query_only = ON. SQLite itself refuses to write.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,randombloband 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": trueso 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(defaultcustomers.email) read as NULL throughrun_sql, including inWHEREclauses, soWHERE email LIKE 'a%'can't be used to guess them.describe_schematells 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 allowedThe 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-mcpOn 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/mcpSettings
All optional, as environment variables:
Variable | Default |
|
|
|
|
|
|
|
|
|
|
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
SELECTon 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 usecursor.fetchmany(max_rows + 1)for the row cap, as here.describe_schemareadsinformation_schema.columns. In the reports, replacestrftime('%Y-%m', ...)withto_char(date_trunc('month', ...), 'YYYY-MM'), and the:nameparameters with%(name)s.Point it at a read replica if one exists.
MySQL
A user with
SELECTonly, plus column-level grants or views for anything sensitive.Per session:
SET SESSION TRANSACTION READ ONLYandSET SESSION max_execution_time = 5000(milliseconds; it applies to SELECT statements).Parameters become
%(name)swith PyMySQL or mysql-connector, andstrftimebecomesDATE_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_alertsmeasures 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_sqlreturns 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 renamesFastMCPtoMCPServerand moves transport settings ontorun(). I haven't ported to it yet.ask.pyis 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 toolsdescribe_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.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| min_days_of_cover | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| by | No | category | |
| end | No | ||
| start | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
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 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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| end | No | ||
| start | No | ||
| group_by | No | month |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| n | No | ||
| end | No | ||
| start | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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.
6 tool updates
v0.1.0- First observed
describe_schema - First observed
inventory_alerts - First observed
refund_rate - First observed
run_sql - First observed
sales_summary - First observed
top_customers
TDQS
Scored across 6 tools
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.
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.
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.
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
Related MCP Connectors
Connect your ads, shop, analytics, social, CRM and finance platforms once, then let Claude, ChatGPT, Cursor or any MCP client read, join and explain your numbers. Public statistics from the World Bank, IMF, Eurostat, OECD, WHO and SEC filings come as context, searchable and chartable from the same tools. Read-only by design, every number carries its source.
Amazon Seller Central, Ads and Vendor Central in ChatGPT & Claude. 106 tools; writes need approval.
Query 40 databases from Claude, ChatGPT, or Cursor — on any device. Read-only, encrypted, audited.
Connect Claude, Cursor, or ChatGPT to your business data. Ask questions, get answers.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables AI to query a business database for customers, orders, and revenue using natural language through safe, well-defined tools.-
- AlicenseNot gradedqualityBmaintenanceEnables 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.1MIT
- AlicenseNot gradedqualityCmaintenanceProvides 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
- FlicenseNot gradedqualityCmaintenanceEnables 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.-