shop-mcp について
shop-mcpは、Shopifyの商品カタログと在庫を調べるLLMエージェント向けの、単一ファイルで動作するPython製MCPサーバーです。依存パッケージはなく、商品検索や詳細確認、SKU・拠点別の在庫照会、低在庫レポートを提供し、プロトコルとデータ処理を確認するセルフテストも備えています。
主な機能
- 01商品検索と詳細確認
- 02SKU・拠点別の在庫照会
- 03在庫しきい値以下のバリエーションを報告
- 04Python標準ライブラリのみを使用
- 05プロトコルと動作のセルフテスト
活用シーン
- 01顧客の問い合わせに合う商品を検索
- 02店舗拠点ごとのSKU在庫を確認
- 03補充が必要なバリエーションを特定
MCP 対応
stdioでMCPクライアントと通信し、Shopifyの商品カタログと在庫を扱う4つの読み取り専用ツールを提供します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
uvx shop-mcpSHOPIFY_SHOP_DOMAINYour store domain, e.g. your-shop.myshopify.comSHOPIFY_ADMIN_TOKENシークレットShopify Admin API access token (shpat_...) with read_products and read_inventory scopesローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。
ツール
4掲載時のツール一覧 · 3 週間前に更新
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
| limit | integer | いいえ | 返す商品の最大数。1~50。 |
| query | string | はい | Shopify の検索構文。例:'title:shirt'、'vendor:Acme'、'status:ACTIVE'、または通常の単語。 |
詳細
shop-mcp
LLM エージェントが Shopify ストアの商品カタログや在庫についての質問に回答できるようにする Model Context Protocol サーバーです。stdio 経由で、単一のファイルから動作し、Python 標準ライブラリのみを使用します。
MCP SDK は不要です。requests も GraphQL クライアントも不要です。インストールに必要なのは python3 shop_mcp.py だけです。
$ python3 shop_mcp.py --self-test
all green: 200 assertions
このコマンドは認証情報もネットワークも必要としません。これがこのリポジトリの要点です。Shopify のトランスポートを境界でモックするのではなく、継ぎ目で置き換えることで、プロトコル層とツール層の両方を実際にテストできます。
PyPI からインストールした場合、同じコマンドは 183 と表示します。17 件のアサーションの差は、チェックが弱いのではなく、パッケージング上の事情によるものです。
$ uvx --from shop-mcp shop-mcp --self-test
all green: 183 assertions
manifest.json(5 件のアサーション)、llms-install.md(11 件)、README.md(1 件)は、意図的に site-packages に含めていません。manifest の entry_point は、インストール済みコピーには存在しないバンドルパスを指定しているため、パッケージに含めると正しいインストールが失敗します。これら3つのグループは、ファイルがない場合は失敗ではなくスキップされるため、件数は変わっても判定は変わりません。200件すべてを実行するには、リポジトリをクローンしてください。
このサーバーは実例であり、製品ラインではありません。既存のデータを扱うstdio MCP サーバーに、セルフテストスイートと、注入された不具合をそのスイートが検出できることの証明を加えた、同じ構成のものを定額制の仕事として作っています: hello532.github.io/services.html、または [email protected] までご連絡ください。このページの内容はすべて無料で利用でき、いずれにしても issue や PR を歓迎します。
プロトコルを手書きする理由
stdio MCP サーバーの障害は、ローカルではすべて見えず、ホスト上ではすべて致命的だからです。以下に挙げる問題はどれも、このファイルで発生しないように対処している実際の不具合で、それぞれを名指ししたアサーションがあります。
- stdout に診断メッセージを出力する。 余計な
print()が1つあるだけで、クライアントの次の解析が壊れます。サーバーを自分で実行しても何も問題がないように見えます。ここでは診断メッセージをすべて stderr に出力し、セッション全体を通して stdout がバイト単位で空のままであることをテストで確認します。 - 通知に応答する。
notifications/initializedにはidがないため、これに返信すると、保留中のリクエストが存在しないメッセージになります。厳格なクライアントはこれをプロトコル違反と見なし、接続を切断します。 id: 0を通知として扱う。msg.get("id")は値がゼロだと偽になるため、リクエスト番号をゼロから振るクライアントでは、最初の呼び出しがひそかに破棄されます。真偽値ではなく、存在の有無を確認します。- ツールの失敗を JSON-RPC エラーとして送る。 JSON-RPC エラーは不正なリクエストに対するものです。実行したツールが失敗した場合は、
isError: trueと理由のテキストを含む通常の結果を返さなければなりません。そうしないと、モデルがメッセージを受け取れず、自分の引数を修正できません。 - 未知の
protocolVersionをそのまま返す。 クライアントがサーバーの知らないリビジョンを求めた場合、それに同意すると、実装している側がいない仕様を双方が使用中だと思い込むことになります。これは仕様自体のデフォルトである2025-03-26にフォールバックし、その旨を伝えます。 - 返信を整形する。 インデント付きの JSON には改行が含まれますが、改行はフレームの区切り文字です。1つのメッセージが、壊れた複数のメッセージになります。
ツール
| ツール | 回答する内容 |
|---|---|
search_products | 「X に該当する商品は何か」— 商品情報と合計在庫 |
get_product | 1商品の詳細と、SKU、価格、在庫を含むすべてのバリエーション |
check_inventory | SKU ごとのロケーション別在庫:販売可能、引当済み、実在庫 |
low_stock_report | 在庫数がしきい値以下のバリエーションを、少ない順に表示 |
各ツールは、店主が実際に尋ねる質問に答えるために選んだものです。対象範囲を広げるのは簡単ですが、モデルがツールを選ぶ精度は下がります。
プロトコルではない正確さ
3つのアサーションでは、単なるエラーよりも深刻な、自信満々に間違った回答につながるミスを検証します。
- 引用符で囲まれていないSKU。
sku:SH 1とsku:"SH 1"は別のクエリです。前者は誤ったバリエーションに黙って一致し、その在庫数を自分の商品在庫として報告します。SKUは引用符で囲み、内部の引用符はエスケープします。 - nullの数量をゼロとして読み取ること。 Shopifyは、在庫を追跡していないバリエーションに対して
nullを返します。これを0に変換すると、補充レポートにいつまでも表示され続けます。在庫未追跡と在庫切れは別の事実であり、区別したまま扱います。 scan_exhausted。low_stock_reportは、上限のある数のバリエーションをスキャンします。スキャンが上限に達した場合、「在庫が少ない商品はない」と「十分に調べていない」は区別がつきません。そのため、結果でどちらなのかを示し、モデルもそのように説明できます。
さらに、Shopifyクライアントに必要でありながら、多くの場合見落とされるトランスポートのルールがあります。THROTTLED のGraphQLレスポンスは 200 で返されますが、成功として読み取るのではなく、再試行する必要があります。401は 再試行してはいけません。待っても無効なトークンは修正されないためです。また、バックオフは実際に間隔を伸ばす必要があります。
検証済みのこと、未検証のこと
セルフテストで、実行のたびに検証されること: ハンドシェイク、フレーミング、通知処理、IDの有無、エラーマッピング、スキーマの厳密性、再試行とバックオフのポリシー、SKUの引用符処理、nullの数量の処理、しきい値の境界、スキャン上限到達を対象とする200件のアサーションです。通信上の形式は、記憶に頼るのではなく、公式のPython用 mcp SDKの types.py(LATEST_PROTOCOL_VERSION、CallToolResult、ServerCapabilities)を基にしています。
未検証のこと: これを実際のShopifyストアに接続して実行したことはありません。このリポジトリに認証情報はなく、記録されたAPIセッションもありません。Shopify Admin GraphQLクエリはドキュメントに記載されたスキーマに沿って書かれており、その周辺のすべてのコードパスはトランスポートのダブルを使ってテストされています。しかし、実際のストアとの往復通信は未検証であり、テスト用ダブルはShopifyの挙動に関する私のモデルであって、Shopifyそのものではありません。
この区別こそが誠実な線引きであり、gpt-ads-feedで引かれている線と同じです。この点を曖昧にするREADMEは、信頼すべきでないものを信頼するよう求めているのです。
失敗を検出できるアサーション
mutation_test.sh はソースのコピーに既知の欠陥を注入し、それぞれについて --self-test が失敗することをアサートして、どのアサーションが検出したかを示します。また、検索パターンが古くなっている場合は NO-OP EDIT のフラグを立てます。適用されないミューテーションは何もテストせず、成功しているように見えるためです。これは、テストスイートを役立たずどころか有害にする失敗モード、つまり信頼されているのに中身がない状態を招きます。
最初の実行でテストスイートの実際の弱点が見つかりました。3つとも同じパターンで、欠陥自体は検出されたものの、名前付きアサーションではなく例外によって検出されていたため、メッセージからは何もわからず、その後のアサーションもすべて実行されませんでした。
そのうち2つは未処理の KeyError: 'result' でした。欠陥によって JSON-RPC エラーに変わった応答をインデックス参照していたためです。結果へのアクセスを形状チェック経由にすることで修正しました。同じ欠陥は今では a tool crash returns a result, so the loop survives: reply is a JSON-RPC error {'code': -32603, ...} と報告され、その後に続く3つのアサーションもそれぞれの判定を報告します。
3つ目は、下のアサーションを意味のあるものにするためだけに存在していた、単独のセットアップ呼び出し S.Tools(c).search_products(...) でした。スロットル分岐を無効にすると例外が発生し、そのアサーションが実行される前にテストが中断されていました。raises() の正確な逆である completes() を使って修正しました。今では欠陥は a 200-with-THROTTLED is survivable, not a hard failure: raised ShopifyError: Throttled [THROTTLED] と報告され、ルールを示しつつ原因も保持します。
サーバーを .mcpb バンドルとしてパッケージ化し、ホストが起動する方法で起動したときにだけ表面化する欠陥が、さらに3つ見つかりました。この方法をテストしたことは一度もありませんでした。
- コードは
SHOPIFY_SHOPを読み取っていましたが、この README とバンドルのマニフェストはどちらも、ユーザーにSHOPIFY_SHOP_DOMAINをエクスポートするよう案内していました。ドキュメントに従った人は、サーバーが永続的に未設定のままになっていました。180個のアサーションはすべて成功しました。コードとドキュメントを比較するものがなかったためです。 - 認証情報が設定されるまで
tools/listが[]を返していたため、ホストには空のサーバーが表示され、壊れていると報告されていました。また、tools/callのわかりやすい no store is configured メッセージには到達できませんでした。呼び出せるものが何も一覧表示されなかったためです。そのコードの上にある docstring は逆の要件を示しており、下のテストは欠陥をアサートしていました。eq(tools, [], ...)。リストの生成に認証情報は必要ありませんでした。descriptors()はインスタンスの状態にまったく触れておらず、現在はstaticmethodになっています。 --self-testはモジュールの docstring で案内されていましたが、サーバーファイルしか含まないバンドル内でクラッシュしていました。現在、バンドルにはテストスイートも含まれています。
最初の修正によって、記録しておく価値のある形でテストハーネスが壊れました。README.md が存在しない場合、新しいアサーションが失敗しました。ハーネスがコピーするファイルは2つだけだったため、すべてのミュータントでそのアサーションが発火しました。実行結果には 17 caught と表示されましたが、そのうち6つは独自のラベルではなく README.md is present として記録されていました。これでは、テストスイートが成功したまま、実際には6つのアサーションが機能していない可能性がありました。README の欠落はパッケージング上の事実であり、コードの欠陥ではありません。重要な比較は、ソースとともに存在するモジュールの docstring に対して実行するようになりました。また、クロスチェックが実際に機能するよう、ハーネスが README をコピーするようにしました。
23個すべてのミューテーションは、何が壊れたかを示すアサーションによって検出され、それぞれ自身のラベルに記録されます。
使用方法
PyPI からインストールする場合 — クローンは不要です。
export SHOPIFY_SHOP_DOMAIN=your-shop.myshopify.com
export SHOPIFY_ADMIN_TOKEN=shpat_... # read_products, read_inventory
uvx shop-mcp # or: pip install shop-mcp && shop-mcp
Claude Desktop/任意の MCP ホストの場合:
{
"mcpServers": {
"shop": {
"command": "uvx",
"args": ["shop-mcp"],
"env": {
"SHOPIFY_SHOP_DOMAIN": "your-shop.myshopify.com",
"SHOPIFY_ADMIN_TOKEN": "shpat_..."
}
}
}
}
代わりに、実行前にソースを読みたい場合はクローンから実行します。これは、依存関係のない単一ファイルにする目的であり、200個のアサーションからなる完全なテストスイートを実行する唯一の方法です。
python3 shop_mcp.py --self-test # 200 here, 183 installed; see above
python3 shop_mcp.py
{ "command": "python3", "args": ["/absolute/path/to/shop_mcp.py"] }
認証情報が設定されていなくても、ハンドシェイクを完了して tools/list を提供し、その後、不足している変数名を示す isError を返します。tools/list を読み取れないホストは「サーバーが壊れています」と報告し、間違った場所を探すことになります。
MIT。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。