Skip to main content
Glama
lampmaster

shop-sql-mcp

by lampmaster

shop-sql-mcp

shop.db SQLiteデータベースへの読み取り専用の分析アクセスを、stdio経由でAIエージェントに提供する小さなMCPサーバーです。

サーバーが行うのは3つのことだけです。テーブルの一覧表示、スキーマの説明、サーバーが強制するページネーション付きで呼び出しごとに1つの読み取り専用SQL文を実行することです。どの結合を行うか、どのように集計するか、いつスキーマを参照するかといったすべての推論はエージェントに委ねられます。

AI Agent
    |
    |  MCP over stdio
    v
shop-sql-mcp
    |
    +-- list_tables
    +-- describe_table
    +-- query_database
    |
    v
read-only SQLite connection
    |
    v
shop.db

要件

  • Node.js 22.5以上(24+を推奨)。サーバーは組み込みのnode:sqliteモジュールを使用するため、コンパイルが必要なネイティブSQLite依存関係はありません。

  • その他のランタイム前提条件はありません。

Related MCP server: MCP SQLite Server (Read-Only)

インストール

npm install

設定

設定は任意です。デフォルトでは、サーバーはプロジェクトルートにあるshop.dbを開きます。

変数

デフォルト

意味

DATABASE_PATH

<project>/shop.db

SQLiteファイルへのパス。相対パスはプロジェクトルートに対して解決されるため、サーバーは起動された作業ディレクトリに依存しません。

ローカルの上書きを保持したい場合は、.env.exampleを.envにコピーしてください。サーバー自体はプレーンな環境変数を読み取ります。.env.example内のANTHROPIC_API_KEY、EVAL_MODEL、EVAL_MAX_STEPSはnpm run evalでのみ使用されます。

ビルド

npm run build

src/をdist/にコンパイルします。

実行

npm start              # runs the built server (dist/index.js)
npm run dev            # runs src/index.ts directly, no build step

サーバーはstdin/stdoutでMCPを話し、stderrには診断情報しか出力しません。そのため、ターミナルで実行するとハングしたように見えますが、それが正しい動作です。MCPホストから起動されることを想定しています。

MCPエージェントに接続する

これをMCPホストの設定(Claude Desktopのclaude_desktop_config.json、Claude Codeの.mcp.json、またはホストに応じた同等のファイル)に追加します。プロジェクトへの絶対パスを使用します:

{
  "mcpServers": {
    "shop-sql": {
      "command": "node",
      "args": ["/absolute/path/to/shop-sql-mcp/dist/index.js"]
    }
  }
}

ビルドせずにソースから実行するには、代わりにTypeScriptエントリポイントを指定します — Node.jsが直接実行します:

{
  "mcpServers": {
    "shop-sql": {
      "command": "node",
      "args": ["/absolute/path/to/shop-sql-mcp/src/index.ts"]
    }
  }
}

別の場所のデータベースを読み取る場合:

{
  "mcpServers": {
    "shop-sql": {
      "command": "node",
      "args": ["/absolute/path/to/shop-sql-mcp/dist/index.js"],
      "env": { "DATABASE_PATH": "/absolute/path/to/other.db" }
    }
  }
}

Claude Codeでは、コマンドラインから登録することもできます:

claude mcp add shop-sql -- node /absolute/path/to/shop-sql-mcp/dist/index.js

ツール

list_tables

引数なし。ユーザーテーブルを返します。内部のsqlite_*テーブルは非表示です。

{
  "tables": [
    { "name": "customers" },
    { "name": "order_items" },
    { "name": "orders" },
    { "name": "products" }
  ]
}

describe_table

{ table: string }

スキーマをSQLiteからライブで読み取ります — ハードコードされたものはありません — そして、列、型、NULL許容性、主キー、外部キーを報告します:

{
  "table": "order_items",
  "columns": [
    { "name": "id", "type": "INTEGER", "nullable": false, "primaryKey": true },
    { "name": "order_id", "type": "INTEGER", "nullable": false, "primaryKey": false }
  ],
  "foreignKeys": [
    { "column": "order_id", "referencesTable": "orders", "referencesColumn": "id" },
    { "column": "product_id", "referencesTable": "products", "referencesColumn": "id" }
  ]
}

不明な名前はクラッシュではなく、回復可能なエラーです:

{ "error": { "code": "TABLE_NOT_FOUND", "message": "TABLE_NOT_FOUND: Table \"foo\" does not exist." } }

注:INTEGER PRIMARY KEYの列はnullable: falseとして報告されます。SQLiteのtable_infoは別のことを示しますが、そのような列はrowidの別名であり、NULLを保持することはできません。

query_database

{ sql: string; limit?: number; offset?: number }

読み取り専用の文を1つ実行します — SELECT ...またはWITH ... SELECT ... — JOIN、WHERE、GROUP BY、HAVING、ORDER BY、サブクエリ、集計、日付フィルタリングのすべてがサポートされています。

{
  "columns": ["category", "revenue"],
  "rows": [["Electronics", 1234567.89]],
  "returnedRows": 1,
  "limit": 100,
  "offset": 0,
  "hasMore": false
}

行はcolumnsの順序の値の配列です。これにより、結果ペイロードをコンパクトに保ち、クエリが同名列を2つ生成した場合でも曖昧さがありません。

失敗は、isErrorが設定された通常のツール結果と、短く実行可能なペイロードとして返されます。エージェントはそれを受けてSQLを修正し、再試行できます:

{ "error": { "code": "SQL_ERROR", "message": "no such column: total" } }

エラーコード:SQL_ERROR、READ_ONLY_VIOLATION、MULTIPLE_STATEMENTS、TABLE_NOT_FOUND、INVALID_ARGUMENT、DATABASE_UNAVAILABLE。スタックトレースが返されることはありません。

ページネーション

ページネーションはモデルのSQLではなく、サーバーによって強制されます。

  • limitのデフォルトは100、最大は500です。offsetのデフォルトは0です。

  • エージェントのクエリはSELECT * FROM (<your sql>) LIMIT ? OFFSET ?としてラップされるため、クエリにLIMIT 100000が含まれていても、limitより多くの行を返すことはできません。

  • サーバーは内部的にlimit + 1行を取得して、2回目のカウントクエリなしにhasMoreを判定し、最大limit行を返します。

  • したがって、1回の呼び出しが500行を超えることはありません。これにより、広範なSELECT *がモデルのコンテキストに殺到しません。

結果をページ送りするには、SQLを同一のまま(決定論的なORDER BY付きで)維持し、hasMoreがtrueの間offsetをlimitずつ進めます。

読み取り専用の安全性

2つの独立したレイヤーがあるため、どちらか一方だけに依存することはありません。

1. SQL検証(src/sqlSafety.ts)。小さなレクサーがコメント、文字列リテラル、引用符付き識別子をスキップし、次を要求します:

  • 文がSELECTまたはWITHで始まること。単純なstartsWith("SELECT")では、有効な読み取り専用CTEを拒否してしまいます。

  • 文がちょうど1つであること(最初の;より後の部分は拒否され、リテラルまたはコメント内の;は区切り記号ではありません)。

  • 禁止キーワードがどこにも現れないこと(CTE内にネストしたものも含む):INSERT、UPDATE、DELETE、CREATE、DROP、ALTER、REPLACE、ATTACH、DETACH、VACUUM、REINDEX、PRAGMA、ANALYZE、BEGIN、COMMIT、ROLLBACK、SAVEPOINT、load_extension、writable_schema。

禁止されたSQLは、常に明示的なエラーで拒否されます — 黙って無視されることも、部分的に実行されることもありません。REPLACE(a, b, c)はスカラー関数として引き続き許可されます。書き込みを伴うのはREPLACE INTO文だけだからです。

2. SQLite接続自体。 shop.dbはnew DatabaseSync(path, { readOnly: true })で開かれます。検証をすり抜けた書き込みが発生しても、SQLiteは*"attempt to write a readonly database"*と拒否します。テストスイートは、バリデータをバイパスして接続上に直接書き込みを発行し、これを検証しています。

不正または禁止されたクエリはツールエラーとして返され、プロセスを終了させることはありません。そのため、セッションは何度失敗しても生き続けます。

テストの実行

npm test

決定的スイートのみを実行します — ネットワーク、APIキー、LLMは使用しません。Node.jsビルトインのテストランナーがTypeScriptソースを直接実行します。カバレッジには次が含まれます:list_tables、describe_table(列、型、NULL許容性、主キー、外部キー、不明なテーブル)、単純なSELECT、フィルタリング、集約、結合、GROUP BY、読み取り専用CTE、日付フィルタリング、ページネーション(デフォルトのlimit、最大limit、offset、hasMoreの境界)、無効なSQL、不明な列とテーブル、INSERT/UPDATE/DELETE/CREATE/DROP/ALTER/REPLACE/ATTACH/DETACH/VACUUM/REINDEX/PRAGMAと複数文の拒否、拒否されたすべての書き込みの後にデータベースがバイト単位で同一であることの証明、そしてstdioを介したエンドツーエンドのMCP呼び出しがエラー後もサーバーが使用可能なことを確認します。

evalを手動で実行

export ANTHROPIC_API_KEY=sk-...
npm run eval

これは手動で起動してください。 実際のLLMに対してstdioで実際のMCPサーバーを起動し、有料のAPI呼び出しを行うため、意図的にnpm testから除外されています。

サーバーを起動し、モデルに3つのMCPツールと、タスクごとに固定されたJSONスキーマを持つsubmit_answerツールを渡し、構造化された回答を自然言語テキストではなく、SQLiteから直接計算された参照値と比較します。タスクには、テーブル発見、マルチステップのスキーマ発見、フィルタリング、集約、結合、顧客の支出額、顧客の注文数、商品の売上、カテゴリの売上高、2025年の売上、そして拒否されなければならない破壊的なリクエストが含まれます(このチェックでは、その後にデータベースが変更されていないことも検証します)。

任意:EVAL_MODEL(デフォルトclaude-sonnet-5)とEVAL_MAX_STEPS(デフォルト12)。いずれかのタスクが失敗すると、終了コードは非ゼロになります。

レイアウト

src/
  index.ts       MCP server: tool registration, stdio wiring, error shaping
  db.ts          read-only connection, path resolution, row/value normalisation
  tools.ts       the three tools: list_tables, describe_table, query_database
  sqlSafety.ts   single-statement read-only SQL validation
tests/
  sqlSafety.test.ts   validator, allowed and forbidden SQL
  tools.test.ts       tools against the real shop.db
  mcp.test.ts         end-to-end over stdio with a real MCP client
eval/
  tasks.ts       eval tasks and their SQLite reference values
  run.ts         LLM + MCP eval runner (manual)
shop.db

依存関係

パッケージ

理由

@modelcontextprotocol/server

公式のMCPTypeScript SDK(v2)。によって提供されるものMcpServerとstdioトランスポートにより、プロトプルを手動実装する必要がありません。

zod

SDKのツール入出力スキーマに必要です。エージェントに機械可読な引数型を公開するものです。

必要なのは、typescript, @types/node

開発のみ:ビルドと型テストのため。

@modelcontextprotocol/client

開発のみ:stdioのエンドツーエンドテストとevalランナーで使用する公式MCPクライアントです。

SQLiteはNode.jsビルトインのnode:sqlite、テストはNode.jsビルトインのテストランナー、evalのHTTP呼び出しはビルトインのfetchを使用します。ドライバ、ORM、クエリビルダー、Webフレームワーク、ロガー、テストフレームワーク、SQLパーサー、LLM SDKはインストールされていません。

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    Exposes any SQLite database as read-only MCP tools for AI assistants, enabling listing tables, describing schemas, and running SELECT queries with filtering, ordering, and pagination.
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to read-only analyze a SQLite e-commerce database, exploring schema and running analytical SQL queries over stdio.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI models to securely analyze SQLite datasets and read local text documents through a local stdio interface. It provides read-only querying, schema discovery, pagination, and timeout protections to prevent data leaks or destructive changes.
    MIT