Skip to main content
Glama

mcp-server-playground

A hands-on MCP (Model Context Protocol) server built over a SQLite mock database, for learning how an AI client discovers and calls your own tools.

The dataset is a fictional general online store — 1,200 customers, 1,358 SKUs across 540 distinct products, 25,000 orders, 61,835 line items and 4,000 reviews, spanning 2024-01 to 2026-07.

Ten categories, deliberately spanning an order of magnitude in price:

家電  食品  日用品  ファッション  書籍  インテリア  スポーツ  文具  ペット用品  コスメ
      ¥160 (パスタソース) … ¥36,480 (ハンディクリーナー 上位モデル)

The data is shaped rather than uniformly random, so aggregating it yields findings instead of noise:

  • Seasonality. December runs ~1.65× a normal month, February ~0.8×.

  • Growth. Volume trends up ~1.8% per month across the whole period.

  • Concentration. A minority of SKUs and a minority of customers account for most of the volume, so rankings and customer segments are not flat.

  • Brands. 9 brands × 60 product types, each brand with its own price positioning. sales_summary groups by brand, so "which brand sells best" is answerable — and the answer is not the obvious one: プレミアムセレクト leads on revenue while デイリープラス takes 70% more orders to reach last place.

  • Price vs volume. 日用品 has the most orders but the least revenue; 家電 is the reverse. Ranking by revenue and by units gives different answers, which is exactly the distinction top_products exposes.

Why this stack

Choice

Reason

TypeScript on Node ≥24.10

Node's type stripping runs .ts files directly — no build step, no dist/

node:sqlite

SQLite is built into Node; no native module to compile

Three transports

stdio, Streamable HTTP, and Streamable HTTP with OAuth 2.1

Few dependencies

@modelcontextprotocol/sdk and zod; express only for the OAuth server

src/server.ts defines what the server exposes; src/stdio.ts and src/http.ts define how it is reached. The tool definitions know nothing about either transport, which is why supporting both cost one extra file.

src/server.ts        tools, resources, prompts  (transport-agnostic)
src/db.ts            connections + read-only guard
src/seed.ts          deterministic mock data
src/stdio.ts         entry point: stdin/stdout
src/http.ts          entry point: Streamable HTTP
src/http-oauth.ts    entry point: Streamable HTTP + OAuth 2.1
src/auth-provider.ts in-memory authorization server for the demo
src/session-store.ts HTTP session bookkeeping: idle timeout + cap

Node 24.10 is the floor because of DatabaseSync.setAuthorizer(), which the read-only connection depends on. Type stripping itself only needs 23.6.

Three entry points, one server.ts. Adding OAuth did not change a single tool definition — that separation is the whole point of the layout.

Related MCP server: BigGo MCP Server

Quick start

npm install
npm run tools        # print everything the server advertises
npm run smoke        # stdio: acts as an MCP client and exercises everything
npm run smoke:http   # Streamable HTTP: same, plus two concurrent sessions
npm run smoke:oauth  # OAuth 2.1: the whole authorization flow, step by step

npm run tools shows exactly what the model receives — name, description and argument schema, nothing more. If a tool reads as ambiguous there, it reads as ambiguous to the model too, which makes it the fastest way to review a tool definition you just wrote.

npm run smoke is the fastest way to see the whole protocol in action: it spawns the server exactly like a real client does, lists the capabilities, calls every tool, reads both resources, fetches a prompt, and finally proves that a restart wipes the database back to its initial state.

There is no separate setup step — the server builds data/shop.db itself on start. npm run seed exists only for rebuilding the file by hand, e.g. to poke at it with the sqlite3 CLI.

A fresh database on every start

The server deletes and rebuilds data/shop.db each time it starts, so every session begins from the same known state and nothing a previous session wrote survives. The seed uses a fixed PRNG value, so "rebuilt" means the identical dataset every time — you can memorise a number from one session and still recognise it in the next.

Rebuilding ~93,000 rows costs about 250ms (all inserts run in one transaction), which is invisible next to process startup.

playground-shop: database reset in 249ms — customers=1200  products=1358  orders=25000  order_items=61835  reviews=4000

To keep data between runs instead, set MCP_SQLITE_PERSIST=1:

{
  "mcpServers": {
    "playground-shop": {
      "command": "node",
      "args": ["src/stdio.ts"],
      "env": { "MCP_SQLITE_PERSIST": "1" }
    }
  }
}

One consequence worth knowing: because each client spawns its own server process, opening MCP Inspector while Claude Code is already connected resets the database underneath Claude Code's session. Run one client at a time, or use MCP_SQLITE_PERSIST=1 when you deliberately want them to share state.

What the server exposes

MCP has three kinds of capability. This server implements all three.

Tools — actions the model chooses to call

Tool

Purpose

list_tables

Every table with its row count. The natural entry point.

describe_table

Column definitions plus three sample rows.

search_products

Keyword, brand, category, price range and stock filters.

search_reviews

Full-text search over review bodies, backed by SQLite FTS5.

customer_orders

Resolve a customer by id/email/name, return orders with items joined.

sales_summary

Revenue aggregated by month, category, brand, prefecture or status.

top_products

Ranking by revenue, units sold or average rating.

add_review

The one tool that writes. Inserts a review row.

There is no raw-SQL tool, on purpose. An earlier version had one, and it is worth understanding why it is gone — see Why no raw-SQL tool.

The two text searches (search_products.keyword, customer_orders.name) split the query on whitespace and require every term, rather than matching the whole string. Word order stops mattering, which matters here because product names are <brand> <product>(<size>) and a model has no reason to guess that order:

"匠工房 加湿器"  →  匠工房 超音波加湿器(標準)
"加湿器 匠工房"  →  same result
"匠工房超音波"   →  no match — terms are matched individually, not fuzzily

% and _ are escaped, so a query of % finds nothing rather than everything.

Results are ordered by sort, which defaults to relevance. There is no real relevance score behind LIKE, so it approximates one with name length: the shortest matching name is the plainest product. That matters because ordering by price put the answer to "find me a ballpoint pen" at ¥1,660 for a premium 5-pack, when a ¥200 single pen also matched:

sort=relevance   グッドデイ ゲルインクボールペン(1個)        ¥200
sort=price_desc  プレミアムセレクト ゲルインクボールペン(5個セット) ¥1,660

Full-text search with FTS5

search_reviews searches review bodies, which LIKE over 4,000 rows would scan linearly. It is backed by an FTS5 virtual table, and getting that working for Japanese took two non-obvious decisions.

The default tokenizer is useless for Japanese. FTS5's unicode61 splits on whitespace and punctuation. A Japanese sentence has neither, so the whole sentence becomes one token and nothing short of an exact match ever hits:

unicode61, indexing "音が静かで満足しています"
  MATCH '静か'   →  0 rows

tokenize='trigram' indexes every 3-character window instead, which has no notion of word boundaries and therefore does not need one:

trigram, same text
  MATCH '音が静か'  →  1 row
  MATCH '静か'      →  0 rows   ← still nothing

Trigram has a hard floor of three characters. Two-character queries — 静か, 破損, 満足, all perfectly ordinary Japanese words — cannot match, ever. The tool therefore routes each term by length: three or more characters go to the index, shorter ones fall back to a LIKE scan. Mixed queries use both, and the response says which path ran:

"動作音が大きくて"  →  1 review(s) via full-text index (動作音が大きくて)
"静か"             →  1 review(s) via LIKE scan
"洗濯 縮み"         →  1 review(s) via LIKE scan

The index earns its place even at this size — measured over 4,000 reviews:

FTS5 MATCH    0.059 ms/query
LIKE %...%    0.574 ms/query     ~10x slower

Two implementation notes worth knowing if you add FTS5 to your own server:

  • The authorizer needs PRAGMA data_version. FTS5 reads it internally to validate its cache. Without it every FTS query fails with a bare authorization denied and nothing indicates which pragma was refused.

  • Shadow tables are hidden from list_tables. An FTS5 table brings five companions (_data, _idx, _content, _docsize, _config). They are storage internals; listing them would waste the model's context and invite it to query them directly.

Writes stay in sync through triggers, so a review added via add_review is immediately findable by search_reviews.

Query terms are quoted before being handed to FTS5, so review text containing FTS5 operators is treated as literal text rather than syntax.

Resources — context the client reads, not calls

  • schema://database — full DDL for every table.

  • table://{table}/sample — first five rows as JSON. A URI template: the server also advertises one concrete URI per table and supports completion, so a client can offer table://orders/sample as an autocomplete suggestion.

Prompts — user-triggered templates

  • monthly_report (month) — draft a sales report for one month.

  • product_deep_dive (keyword) — investigate one product end to end.

In Claude Code these appear as /mcp__playground-shop__monthly_report.

Why no raw-SQL tool

A run_sql tool taking a SELECT string is the obvious way to make a database useful to a model, and it was the first tool this project had. Removing it was the single biggest security improvement, so the reasoning is worth recording.

Guarding it took three layers, and measurement showed each one mattered:

Attack

Stopped by

Why

SELECT 1; DROP TABLE reviews

SQLite itself

prepare() compiles only the first statement

DELETE FROM reviews

all three layers

write, in every sense

/* hi */ DELETE FROM ...

regex

but it also rejected valid commented queries

ATTACH DATABASE '/etc/passwd'

regex + authorizer

would expose other database files

PRAGMA database_list

regex + authorizer

leaks absolute filesystem paths

SELECT readfile('/etc/passwd')

nothing — luck

the function simply does not exist in node:sqlite

unbounded WITH RECURSIVE

authorizer only

hung the server for as long as it was left running

Two attacks had no answer at all:

  • SELECT * FROM customers reads every row. It is a valid query, so no layer has grounds to refuse it. Allowing raw SQL over real data means allowing the model to read all of that data.

  • A big enough cross join blocks the process. DatabaseSync is synchronous, so a slow query freezes the server. Measured: worker_threads + terminate() does not help, because the loop is inside SQLite's C code and never returns control to V8. Only child_process + SIGKILL actually stops it — which is a lot of machinery to bolt onto one tool.

The purpose-built tools need none of this. Their input is typed arguments, the SQL is written by hand in server.ts, and business rules (excluding cancelled orders, sensible defaults) are enforced in one place instead of hoped for. They also cost fewer tokens and cannot produce a syntax error.

The trade-off is real: questions no tool covers now go unanswered instead of being improvised. That is the intended behaviour — the server's instructions tell the model to say so rather than guess.

Safety model

The database still gets defence in depth, because a bug in a hand-written query should fail loudly rather than corrupt data.

A SQLite authorizer. The read-only connection installs setAuthorizer(), which SQLite consults for every operation the parser attempts. Only SQLITE_SELECT, SQLITE_READ and SQLITE_FUNCTION pass, plus PRAGMA table_info for describe_table. SQLITE_RECURSIVE is denied, so an unbounded recursive CTE can never start.

A read-only connection. readOnly: true means a write fails even if the authorizer were misconfigured. add_review uses a separate writable connection.

Tool annotations. readOnlyHint: true on every query tool and readOnlyHint: false on add_review — that is the signal a client uses to decide whether to ask the user for confirmation.

Still not covered: no authentication on the HTTP transport (see the Streamable HTTP section), and the seeded data is fictional, so nothing here is tuned for real personal data. For that, SQLITE_IGNORE in the authorizer can mask individual columns — returning NULL for, say, customers.email even under SELECT * and in WHERE clauses.

Connecting a client

.mcp.json in this repository already declares the server. Start Claude Code from this directory and approve the server when prompted:

cd ~/projects/mcp-server-playground
claude

Then /mcp shows the connection status and the available tools.

Claude Code — available everywhere

claude mcp add playground-shop --scope user -- node ~/projects/mcp-server-playground/src/stdio.ts

Use an absolute path: the client sets its own working directory.

Claude Code — one-off, non-interactive

claude -p "2026年6月のカテゴリ別売上を教えて" \
  --mcp-config ~/projects/mcp-server-playground/.mcp.json \
  --allowedTools "mcp__playground-shop__sales_summary,mcp__playground-shop__top_products"

Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "playground-shop": {
      "command": "node",
      "args": ["/Users/YOU/projects/mcp-server-playground/src/stdio.ts"]
    }
  }
}

Streamable HTTP

Start the server yourself and let clients connect over the network:

npm run start:http
# playground-shop MCP server listening on http://127.0.0.1:4871/mcp
claude mcp add --transport http playground-shop http://127.0.0.1:4871/mcp

Or non-interactively:

claude -p "レビュー平均評価の高い商品トップ3は?" \
  --mcp-config '{"mcpServers":{"playground-shop":{"type":"http","url":"http://127.0.0.1:4871/mcp"}}}' \
  --allowedTools "mcp__playground-shop__top_products"

How it differs from stdio:

stdio

Streamable HTTP

Process

One per client, spawned by the client

One long-lived server, started by you

Clients

Exactly one

Many, each with its own session

Addressing

A command line

A URL

Lifetime

Dies with the client

Runs until you stop it

Database reset

Every client launch

Once per server start — clients share the data

Endpoints: POST /mcp for JSON-RPC, GET /mcp for the server-to-client SSE stream, DELETE /mcp to end a session, plus a non-MCP GET /health that reports the active session count.

Sessions are stateful: initialize returns an mcp-session-id header that later requests must carry, and each session gets its own McpServer instance. DNS rebinding protection is on, so the Host header must match the address the server is listening on — a browser page cannot quietly reach your local server.

There is no authentication on this entry point. It binds to 127.0.0.1 deliberately. For an authenticated server, see the next section.

Environment: MCP_HTTP_PORT (default 4871), MCP_HTTP_HOST (default 127.0.0.1).

Sessions are bounded. A plain Map<sessionId, transport> is enough to make the protocol work, but clients disconnect without sending DELETE /mcp — a crashed process, a closed laptop — and each abandoned session pins an McpServer, a transport and any open SSE stream for the life of the process. src/session-store.ts adds an idle timeout (30 min) and a cap (100 sessions, past which new ones get a 503). Reads refresh the timer, so an active session is never swept.

Streamable HTTP with OAuth 2.1

npm run start:oauth
# playground-shop MCP server (OAuth) listening on http://127.0.0.1:4872/mcp
claude mcp add --transport http playground-shop http://127.0.0.1:4872/mcp

Claude Code then runs the whole flow itself — discovery, dynamic client registration, opening a browser for the consent screen, token exchange and refresh. You never paste a client id or a token anywhere.

npm run smoke:oauth walks the same flow by hand, one labelled step at a time, which is the fastest way to see what is actually happening:

1.  unauthenticated request  → 401 + WWW-Authenticate: Bearer …, resource_metadata="…"
2.  GET /.well-known/oauth-protected-resource/mcp   → which server guards this API
3.  GET /.well-known/oauth-authorization-server     → where /authorize, /token, /register are
4.  POST /register                                  → dynamic client registration (RFC 7591)
5.  GET /authorize?…&code_challenge=…               → consent screen
6.  user approves                                   → redirect back with ?code=…
7.  POST /token with the PKCE verifier              → access + refresh token
8.  bad token                                       → 401 invalid_token
9.  MCP client with the access token                → tools/list, tools/call work
10. read-only token calls add_review                → rejected, missing playground-shop:write
11. full-scope token calls add_review               → succeeds
12. POST /token grant_type=refresh_token            → new access token

Scopes. playground-shop:read is required for any MCP request; add_review additionally requires playground-shop:write. The check lives in server.ts and reads extra.authInfo, which the transport populates. Under stdio and the plain HTTP server authInfo is undefined and the tool stays open — the check applies exactly where there are scopes to check, without making the tool definition transport-aware.

Environment: MCP_HTTP_PORT (default 4872), MCP_HTTP_HOST (default 127.0.0.1).

Not production ready

The authorization server is a teaching implementation:

  • Everything is in memory. Restart the process and every registered client and issued token is gone.

  • No user authentication. The consent screen has no login — anyone who can reach /authorize can approve. A real server authenticates a user first and binds the token to them.

  • Tokens are opaque random strings in a Map, not signed JWTs, so they cannot be verified by anything but this process.

  • Plain HTTP on localhost. OAuth 2.1 requires TLS everywhere but loopback.

  • Anyone may register. Dynamic client registration is unauthenticated and unthrottled beyond the SDK's default rate limiting.

What it does demonstrate faithfully is the protocol: discovery, PKCE, single-use authorization codes, scope enforcement and refresh.

MCP Inspector — the GUI debugger

npm run inspect

Opens a browser UI where you can see the raw JSON-RPC traffic, call tools with arbitrary arguments and inspect errors. This is the tool to reach for while developing a new capability.

Things to try

  1. Ask "先月いちばん売れた商品は?" and watch which tool the model picks.

  2. Ask something no tool covers ("都道府県ごとの平均客単価は?") and watch what the model does when it cannot answer. This is the cost of dropping raw SQL — decide for yourself whether a prefecture_stats tool is worth adding.

  3. Ask it to delete all reviews, and watch it discover there is no such tool.

  4. Ask it to post a 5-star review, and see the client ask for confirmation because of readOnlyHint: false. Restart the client and the review is gone.

  5. Delete a tool description in src/server.ts, restart, and observe how much worse the tool selection gets. Descriptions are the model's only documentation.

Environment

  • MCP_SQLITE_DB — override the database path. Defaults to data/shop.db resolved relative to the project root, not the working directory.

  • MCP_SQLITE_PERSIST=1 — skip the reset and reuse the existing database. Still seeds it if the file does not exist yet.

Gotchas worth knowing

  • stdout belongs to the protocol. A stray console.log in server code corrupts the JSON-RPC stream and the client drops the connection. Log to stderr instead — src/stdio.ts does.

  • The client controls the working directory. Always resolve file paths from import.meta.dirname, as src/db.ts does.

  • Tool descriptions are the interface. The model sees only the name, description and JSON schema. A vague description is a broken tool.

Available Tools

8 tools
add_reviewAdd a product reviewB

Insert a new review row for a product. This modifies the database.

ParametersJSON Schema
NameRequiredDescriptionDefault
ratingYes
commentNo
product_idYes
customer_idYes

TDQS

B3.2/5.0
Behavior3/5

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

Annotations already indicate readOnlyHint=false, and the description's statement 'This modifies the database' confirms the write behavior without adding new context. It does not disclose idempotency implications, error handling, or any side effects beyond the insertion itself.

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 description is concise and front-loaded with the core action, but the second sentence 'This modifies the database' is redundant with the already clear 'Insert' action. It could be trimmed to a single sentence without losing essential information.

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

Completeness2/5

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

The tool has four parameters and no output schema, so the description needs to provide substantial context about required fields, constraints, or return behavior. It only gives a minimal 'Insert a new review row' statement, leaving the agent to infer the purpose and implications of each parameter and the result of the operation.

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

Parameters1/5

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

With 0% schema description coverage, the description should compensate by explaining what the parameters represent, but it only mentions 'a product' and ignores customer_id, rating, and comment. The schema itself defines these parameters, yet the description fails to add any explanatory value about their meaning or how they map to the review insertion.

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 clearly states the action ('Insert') and the resource ('a new review row for a product'), making it easy for an agent to know exactly what the tool does. This distinctness is reinforced by the sibling tool 'search_reviews', which is read-oriented, whereas 'add_review' is explicitly an insertion.

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?

The description implies when to use the tool (when adding a review) through the verb 'Insert', but it does not explicitly mention alternatives or exclusion criteria. There is no guidance on conditions like product existence or when to prefer 'search_reviews' for reading reviews.

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

customer_ordersCustomer order historyA
Read-only

Look up a customer by id, email or partial name, and return their orders with line items already joined.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameNoPartial match against the customer name. Space-separated terms are ANDed and order does not matter, so both "佐藤 陽菜" and "陽菜 佐藤" find 佐藤 陽菜.
emailNo
limitNoMaximum number of orders to return.
customer_idNo

TDQS

A4/5.0
Behavior4/5

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

Annotations already declare readOnlyHint=true, so safety is clear. The description adds useful behavioral context beyond annotations by stating lookup keys ('id, email or partial name') and the return characteristic 'line items already joined.' It does not disclose multi-match handling or output ordering, but these are less critical for a read-only lookup tool.

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?

Single sentence with no wasted words. The verb-led structure front-loads the main action, and the clause about line items adds key return information efficiently.

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?

For a simple read-only lookup tool with no output schema, the description provides adequate context: what resource is queried, by what keys, and what the result includes. Minor gaps include behavior for multiple customer matches and parameter precedence, but the description is still sufficient for basic usage.

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 50%; description partially compensates by naming 'id, email or partial name' as lookup criteria. However, email and customer_id formats are not described, and the description does not clarify behavior when multiple parameters are supplied or when partial names match multiple customers. Name semantics are fully covered 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?

Description uses specific verb 'look up' and resource 'customer' with clear return value: 'return their orders with line items already joined.' Clearly distinguishes from sibling tools like search_products and sales_summary, which are product/revenue-focused, not customer-focused.

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?

Description implies usage for retrieving a customer's order history but provides no explicit when-to-use guidance or alternatives. It does not compare against sibling tools like search_products or sales_summary, nor does it state exclusions or prerequisites.

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

describe_tableDescribe tableA
Read-only

Show the column definitions and three sample rows for one table.

ParametersJSON Schema
NameRequiredDescriptionDefault
tableYesTable name, e.g. "orders". Use list_tables to discover valid names.

TDQS

A4.3/5.0
Behavior4/5

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

The annotations already declare readOnlyHint=true, and the description aligns with this by using 'Show'. It adds a specific behavioral detail—returning three sample rows—which goes beyond the annotation and helps the agent set expectations. No contradictions or hidden side effects are implied.

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?

The description is a single, front-loaded sentence: 'Show the column definitions and three sample rows for one table.' It contains zero filler and immediately conveys the action, scope, and output format.

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

Completeness5/5

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

For a simple read-only tool with one well-documented parameter and safety annotations, the description fully covers the essential behavior: column definitions and sample rows. It's complete without needing to explain return values in detail, and the absence of an output schema is compensated by the description's clarity.

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 100%, with the 'table' parameter having a helpful description including an example and a reference to list_tables. The tool description adds no further parameter information, but the schema already carries the burden, so the baseline of 3 is appropriate.

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 uses the specific verb 'Show' and identifies the resource as 'column definitions and three sample rows for one table', making the tool's function unambiguous. This clearly distinguishes it from sibling tools like list_tables, which lists table names, and the various search/query tools.

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?

While the description doesn't explicitly state 'when to use' or name alternatives, the context is clear: to inspect a table's structure. The parameter description adds useful guidance by recommending list_tables to discover valid names, providing a natural workflow. No competing sibling exists, so no exclusions are needed.

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

list_tablesList tablesA
Read-only

List every table in the store database with its row count. Start here when exploring.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.3/5.0
Behavior3/5

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

The annotation readOnlyHint=true already signals this is a safe read operation. The description adds scope ('every table') and output detail ('with its row count'), which is useful. However, it does not disclose potential performance implications of counting rows across all tables, or whether any filtering/sorting is applied. With annotations covering safety, this is adequate but not rich.

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?

The description is two sentences, immediately front-loads the primary action ('List every table'), and provides supplementary guidance ('Start here'). Every word earns its place with no redundancy.

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

Completeness5/5

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

For a zero-parameter, read-only listing tool, the description fully covers purpose, scope, output content, and usage context. No output schema exists, but the description's mention of 'row count' sufficiently indicates the return shape. Sibling tools are distinct and the entry-point guidance completes the context.

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 has zero parameters and schema coverage is 100%, so there is nothing for the description to add about parameters. Baseline for zero-parameter tools is 4, and the description appropriately focuses on behavior instead of parameters.

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 clearly states the tool's action ('List every table'), the exact resource ('store database'), and the result ('with its row count'). This distinguishes it from sibling tools like describe_table (which likely describes a specific table's schema) and search_products (which searches data).

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?

'Start here when exploring' gives explicit contextual guidance for when to use this tool as an entry point. It does not explicitly enumerate when not to use it or name alternatives, but the context is clear enough to guide selection among siblings.

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

sales_summarySales summaryA
Read-only

Aggregate sales over a date range, grouped by month, product category, brand, customer prefecture or order status. Cancelled orders are excluded unless grouping by status.

ParametersJSON Schema
NameRequiredDescriptionDefault
toNoInclusive end date. Defaults to the latest order.
fromNoInclusive start date. Defaults to the earliest order.
group_byNomonth

Output Schema

ParametersJSON Schema
NameRequiredDescription
toYes
fromYes
rowsYes
group_byYes
total_salesYes
total_ordersYes

TDQS

A4.4/5.0
Behavior4/5

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

Annotations already declare readOnlyHint=true, so the safety profile is covered. Description adds meaningful behavioral detail: cancellation exclusion rule and its exception when grouping by status, which is beyond the structured data.

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?

One concise sentence packs the primary action, parameters, and an edge case. No filler, front-loaded, and all information is relevant.

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?

With an output schema present and readOnlyHint annotation, the description adequately covers core behavior and an important edge case. It doesn't mention date-range basis or output format, but these are likely self-evident or handled by the output 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?

Schema descriptions cover from/to, but group_by only has an enum. The description clarifies the behavioral impact of group_by=status (cancelled orders are included), adding semantic value that the schema alone does not convey.

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?

Clear verb 'Aggregate' with resource 'sales' and explicit scope (date range, grouping dimensions). The description immediately distinguishes it from sibling tools like top_products or search_products by focusing on aggregation rather than lookup or ranking.

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?

Provides clear context for when to use the tool (aggregating sales over a date range) but does not explicitly name alternatives or exclusions. Agent can infer from siblings, but no direct comparison is given.

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

search_productsSearch productsB
Read-only

Find products by keyword, category, price range or stock availability.

ParametersJSON Schema
NameRequiredDescriptionDefault
sortNorelevance ranks shorter names first, which surfaces the plain "<brand> <keyword>" product ahead of multi-packs and premium variants. Use price_asc/price_desc for cheapest/most expensive, rating for best-reviewed.relevance
brandNoExact brand filter.
limitNo
keywordNoPartial match against the product name. Space-separated terms are ANDed and word order does not matter, so "匠工房 加湿器" finds "匠工房 超音波加湿器(標準)". Product names are formatted "<brand> <product>(<size>)", so a brand name alone lists that brand.
categoryNoExact category filter.
max_priceNo
min_priceNo
in_stock_onlyNoExclude products whose stock is 0.

TDQS

B3.2/5.0
Behavior2/5

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

Annotations already declare readOnlyHint and openWorldHint, so the description does not need to repeat that. However, the description adds no additional behavioral context—it does not mention return format, pagination, sorting defaults, or any side effects. It is a bare-bones statement with no extra transparency beyond what annotations provide.

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?

The description is a single, front-loaded sentence that directly states the action and main attributes. Every word contributes meaning; there is no redundancy or filler. It is exceptionally concise while conveying the essential purpose.

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

Completeness2/5

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

Given the tool has 8 parameters and no output schema, a one-sentence description is insufficient. It omits any mention of sorting, limit, or return shape, and does not provide guidance on how to combine filters. Although the schema covers parameter details, the description lacks the contextual richness needed to understand the tool's full behavior, especially in the absence of an output schema.

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?

The description lists key filter types (keyword, category, price range, stock availability) which roughly correspond to schema properties, and 'price range' usefully implies the pair of min_price and max_price. However, with 63% schema description coverage, the description adds only marginal meaning beyond the rich parameter descriptions already present in the schema. It does not explain the detailed semantics like keyword matching behavior.

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 clearly states the tool's function: 'Find products by keyword, category, price range or stock availability.' It names a specific resource (products) and the main filter dimensions, which distinguishes it from sibling tools like 'search_reviews' that target a different resource.

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?

The description provides no explicit guidance on when to use this tool versus alternatives. It does not mention sibling tools, use cases, or exclusions. While the resource and filters imply a product search use case, there is no direct comparison or exclusions, leaving the selection process to inference.

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

search_reviewsSearch review textA
Read-only

Full-text search over review bodies. Returns matching reviews with the product they are about. Use this for questions about what customers say ("どの商品で破損の報告が多い?"), as opposed to search_products which only matches product names.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
queryYesText to find in review bodies. Space-separated terms are ANDed. Queries of three or more characters use the full-text index; shorter ones fall back to a slower scan.
max_ratingNoOnly reviews at or below this rating.
min_ratingNoOnly reviews at or above this rating.

TDQS

A4.4/5.0
Behavior4/5

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

Annotations already declare readOnlyHint=true, so the agent knows this is a safe read operation. The description adds valuable context beyond that: it returns 'matching reviews with the product they are about', and the Japanese example clarifies the intended query type. No contradiction exists. However, it stops short of describing result ordering, pagination, or edge cases, so a 4 is appropriate.

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?

The description is exactly two sentences, front-loads the core function, and includes a usage example plus sibling differentiation. Every word is purposeful; there is no redundancy or filler, making it highly concise and well-structured.

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?

For a read-only search tool with 4 parameters, the description adequately covers what it does, when to use it, and what it returns. The schema supplies parameter details, and annotations cover safety. The only slight gap is the lack of mention of rating filters, but these are visible in the schema, so the overall context is complete enough for an agent to correctly invoke it.

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 75%, and the schema already provides detailed descriptions for query, min_rating, and max_rating, including the ANDing behavior and full-text index fallback for short queries. The tool description itself adds no parameter-level information, so it does not compensate for the undocumented limit parameter. Baseline of 3 is correct since the schema carries the semantic burden.

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 opens with 'Full-text search over review bodies', which is a specific verb+resource pairing that immediately identifies the tool's function. It further differentiates from sibling tool search_products by explicitly stating it searches review content rather than product names, making the purpose unmistakable.

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

Usage Guidelines5/5

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

The description explicitly states when to use this tool: 'Use this for questions about what customers say', and contrasts it with search_products which 'only matches product names'. This provides clear when-to-use and alternative guidance, fulfilling the dimension fully.

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

top_productsTop productsA
Read-only

Rank products by revenue, units sold or average review rating over a date range.

ParametersJSON Schema
NameRequiredDescriptionDefault
toNo
fromNo
limitNo
metricNorevenue

TDQS

A4/5.0
Behavior3/5

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

Annotations already declare readOnlyHint=true, so the main safety profile is covered. The description adds context about the ranking criteria, but it does not disclose behavioral details like sort order, handling of missing date ranges, or what data is included. With annotations, the bar is lower, and the description adds some value, but not rich behavioral context, justifying a 3.

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?

The description is a single, front-loaded sentence that covers all key aspects: the action (rank), the resource (products), the metrics (revenue/units/rating), and the time scope (date range). Every word earns its place, with no redundancy or fluff.

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?

For a simple read-only ranking tool, the description is mostly complete: it specifies the purpose, ranking metrics, and date range. It does not describe the return format, but the tool likely returns a ranked list, which is implied. The optional parameters and their defaults are defined in the schema, so the description covers the essential usage context.

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 does explain the 'metric' enum values (revenue, units, rating) and the 'from/to' date range, but it does not describe the 'limit' parameter at all. The parameter names provide some self-evident meaning, but a fuller explanation of all parameters would elevate the score.

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 clearly states the tool's function with a specific verb and resource: 'Rank products by revenue, units sold or average review rating over a date range.' It distinguishes itself from siblings like 'search_products' (rank vs. search) and 'sales_summary' (product-level ranking vs. aggregate summary).

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?

The description provides clear context for when to use the tool: when you need to rank products by one of three specific metrics within a date range. It does not explicitly mention alternatives or exclusions, but the context is unmistakable, aligning with a 'clear context, no exclusions' rating.

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. 8 tool updatesv0.1.0
    • First observedadd_review
    • First observedcustomer_orders
    • First observeddescribe_table
    • First observedlist_tables
    • First observedsales_summary
    • First observedsearch_products
    • First observedsearch_reviews
    • First observedtop_products

TDQS

A3.9/5.0

Scored across 8 tools

Disambiguation5/5

Each tool targets a distinct resource and action: table exploration, product lookup, customer orders, sales aggregation, product ranking, review search, review insertion, and table listing. The description for search_reviews explicitly contrasts it with search_products, eliminating any ambiguity.

Naming Consistency3/5

Names are all lowercase with underscores, but conventions mix: four use verb_noun (describe_table, search_products, add_review, list_tables) while the rest are noun phrases (customer_orders, sales_summary, top_products). This is readable but not a uniform pattern.

Tool Count5/5

Eight tools is well-scoped for an e-commerce analytics/playground server. Each tool covers a distinct capability (exploration, search, reporting, review handling) without bloat or thinness.

Completeness4/5

The tool surface covers exploration, search, analytics, and review insertion comprehensively. Minor gaps exist (no update/delete for reviews, no CRUD for products/customers), but these are likely outside the server's read-oriented analytics purpose.

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • -
    license
    Not graded
    quality
    A
    maintenance
    A Model Context Protocol (MCP) server implementation that provides database interaction and business intelligence capabilities through SQLite. This server enables running SQL queries, analyzing business data, and automatically generating business insight memos.
    90,196
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    A Model Context Protocol server that enables AI assistants to interact with a complete e-commerce application, providing authentication, product browsing, and shopping cart management through standardized MCP tools.
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    A Python-based MCP server that exposes SQLite database CRUD and search operations on Users, Products, and Orders as tools for AI clients like Claude Desktop.
    -