MCPでAIエージェントをレシートローラーに接続する
この記事では、AIエージェントをレシートローラーのMCPサーバーに接続し、売上・商品・予約などのデータを会話から扱えるようにします。前半は Claude をコネクタとして接続する手順、後半は自分で MCP クライアントを作る開発者向けに、メタデータの取得、動的クライアント登録、認可コード+PKCE によるトークン取得、curl での initialize / tools/list / tools/call の呼び出し、Node.js スクリプトでの自動化までを順に試します。
はじめに必要なもの
- レシートローラーのアカウントと、そのアカウントが所属するビジネスアカウント。ツールが返すのは、そのビジネスアカウントにある実データです(テスト用の環境はまだありません)。
- MCP 接続のために開発者ポータルでアプリを登録する必要はありません。クライアントは動的クライアント登録で自分を登録します。
- 後半を試す場合は
curl、openssl、Node.js 20 以降。
ツールは、接続したユーザー本人のアクセストークンでレシートローラーの API をそのまま呼び出します。そのため、API 側のスコープチェックと権限チェックがそのまま適用されます。承認されていないスコープが必要な操作や、ユーザーの役割で許可されていない操作(たとえば売上を見る権限のない店舗の売上)は実行できません。
接続情報
| 項目 | 値 |
|---|---|
| MCP エンドポイント | https://mcp.receiptroller.io/mcp(/mcp まで必要です。ルートへの POST は 405 になります) |
| 通信方式 | HTTP POST、JSON-RPC 2.0。プロトコルバージョン 2025-03-26 |
| 認可エンドポイント | https://receiptroller.io/oauth/authorize |
| トークンエンドポイント | https://receiptroller.io/api/v1/auth/token |
| クライアント登録 | POST https://mcp.receiptroller.io/register |
要求されるスコープ
MCP クライアントが要求できるスコープは次の 14 個です。同意画面ではこのうちクライアントが要求したものが並びます。
| スコープ | 主に使うツール |
|---|---|
sales.read | sales_get_kpis などの sales_* |
store.products.read / store.products.write | pim_list_products、pim_get_product / pim_create_product、pim_update_product |
store.orders.read / store.orders.write | oms_* |
store.inventory.read / store.inventory.write | wms_* |
store.reservations.read、store.memberships.read、store.passes.read | reservation_*、membership_list_members、pass_list_holders(閲覧のみ) |
sns.content.read、sns.content.write、sns.audience.read、analytics.read | sns_* |
各スコープの意味は OAuthスコープ一覧と利用可否 を参照してください。
パート1:Claude を接続する
- Claude(claude.ai または Claude Desktop)のコネクタ設定で、カスタムコネクタを追加します。
- URL に
https://mcp.receiptroller.io/mcpを入力して保存します。 - 接続を始めるとブラウザでレシートローラーのログイン画面が開くので、ログインします。
- 同意画面を確認して「許可する」を押します。Claude の画面に戻ると接続済みになります。
同意画面には、接続するクライアントの名前と「あなたのレシートローラーアカウントへのアクセスを要求しています。ビジネスアカウントはリクエストごとに選択します。」という説明、そして「要求されるアクセス権限」としてスコープが分類ごとにチェックボックスで並びます。すべてチェック済みで表示され、外したスコープは許可されません。すべて外して許可すると接続は拒否扱いになります。ビジネスアカウントはこの画面では選ばず、ツールを呼ぶたびに organizationId で指定します。
試してみる
| 話しかける例 | 呼ばれるツール |
|---|---|
| 「私のビジネスアカウント全体の今月のKPIを、先月との比較つきで教えて」 | list_my_organizations → sales_get_kpis |
| 「商品マスタから、名前かSKUに『コーヒー』を含む商品を一覧にして」 | pim_list_products(search) |
| 「渋谷店の明日の予約を一覧にして」 | list_my_stores → reservation_list(from / to) |
売上のツールは、ビジネスアカウント全体ならビジネスアカウントの管理者の役割、店舗単位ならその店舗の売上を見られる役割が必要です。権限が足りないと、ツールは Upstream 403 で始まるエラーを返します。
パート2:自分の MCP クライアントから接続する
ここからは、Claude が裏で行っている手順を curl で一つずつ実行します。
ステップ1:メタデータを取得する
curl https://mcp.receiptroller.io/.well-known/oauth-protected-resource
{
"resource": "https://mcp.receiptroller.io/mcp",
"authorization_servers": ["https://mcp.receiptroller.io"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["sns.content.read", "sns.content.write", "...", "store.passes.read"]
}
同じ内容は /.well-known/oauth-protected-resource/mcp でも返ります。次に認可サーバーのメタデータを取得します。
curl https://mcp.receiptroller.io/.well-known/oauth-authorization-server
{
"issuer": "https://mcp.receiptroller.io",
"authorization_endpoint": "https://receiptroller.io/oauth/authorize",
"token_endpoint": "https://receiptroller.io/api/v1/auth/token",
"registration_endpoint": "https://mcp.receiptroller.io/register",
"scopes_supported": ["sns.content.read", "...", "store.passes.read"],
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"token_endpoint_auth_methods_supported": ["client_secret_post", "none"],
"code_challenge_methods_supported": ["S256", "plain"]
}
認可とトークンのエンドポイントは mcp.receiptroller.io ではなく receiptroller.io にあります。
ステップ2:クライアントを登録する
curl -X POST https://mcp.receiptroller.io/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "My MCP Client",
"redirect_uris": ["http://localhost:8765/callback"]
}'
201 で次のレスポンスが返ります。
{
"client_id": "mcp_xxxxxxxxxxxxxxxxxxxx",
"client_secret": "{client_secret}",
"client_id_issued_at": 1793000000,
"client_secret_expires_at": 0,
"redirect_uris": ["http://localhost:8765/callback"],
"client_name": "My MCP Client",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "client_secret_post"
}
client_secretはこのレスポンスでしか確認できません。保存しておいてください。- 保存されるリダイレクトURLは
redirect_urisの最初の 1 件だけです。認可リクエストとトークン交換では、その値を完全一致で使います。 client_nameは同意画面に表示されます。省略するとUnnamed MCP clientになります。redirect_urisが空だと 400invalid_redirect_uriになります。
ステップ3:PKCE の値を作る
code_verifier=$(openssl rand -base64 48 | tr -d '=+/\n' | cut -c1-64)
code_challenge=$(printf '%s' "$code_verifier" | openssl dgst -sha256 -binary | openssl base64 | tr '+/' '-_' | tr -d '=\n')
echo "$code_verifier"; echo "$code_challenge"
ステップ4:ブラウザで認可する
次のURLをブラウザで開きます(改行は読みやすさのためです。実際は 1 行にし、値は URL エンコードしてください)。
https://receiptroller.io/oauth/authorize
?response_type=code
&client_id={client_id}
&redirect_uri=http%3A%2F%2Flocalhost%3A8765%2Fcallback
&scope=sales.read%20store.products.read%20store.reservations.read
&state={state}
&code_challenge={code_challenge}
&code_challenge_method=S256
ログインして「許可する」を押すと、http://localhost:8765/callback?code={code}&state={state} にリダイレクトされます。「拒否する」を押した場合は ?error=access_denied が付きます。認可コードの有効期限は 10 分で、1 回しか使えません。
| エラー(400) | 原因と対処 |
|---|---|
unsupported_response_type | response_type=code を指定してください。 |
invalid_client | client_id が登録されていません。ステップ2の値を確認してください。 |
invalid_redirect_uri | 登録した最初の redirect_uris と完全一致していません。 |
ステップ5:コードをトークンに交換する
curl -X POST https://receiptroller.io/api/v1/auth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d grant_type=authorization_code \
-d code={code} \
-d redirect_uri=http://localhost:8765/callback \
-d client_id={client_id} \
-d client_secret={client_secret} \
-d code_verifier={code_verifier}
{
"access_token": "{access_token}",
"refresh_token": "{refresh_token}",
"token_type": "Bearer",
"expires_in": 28800,
"scope": "sales.read store.products.read store.reservations.read"
}
レスポンスには互換用に accessToken などのキャメルケースのキーも含まれます。アクセストークンの有効期限は 8 時間です。メタデータには none も載っていますが、動的登録したクライアントはシークレットを持つクライアントとして扱われるため、client_secret を必ず送ってください。
| エラー(401) | 原因と対処 |
|---|---|
invalid_client | client_id または client_secret が違います。 |
invalid_grant | コードが期限切れ・使用済み、redirect_uri が認可時と違う、code_verifier がない、または PKCE の検証に失敗しました。error_description で理由を確認し、ステップ4からやり直してください。 |
このエンドポイントは invalid_grant でも 400 ではなく 401 を返します。期限が切れたら grant_type=refresh_token と refresh_token、client_id、client_secret で更新できます。詳しくは はじめてのAPI呼び出し:アプリ登録から取引一覧の取得まで を参照してください。
ステップ6:initialize
curl -X POST https://mcp.receiptroller.io/mcp \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-03-26",
"capabilities": { "tools": {} },
"serverInfo": { "name": "Receipt Roller MCP", "version": "0.1.0" }
}
}
レスポンスは毎回 1 つの JSON で返ります。セッションIDのヘッダーやストリーミングはなく、各リクエストは独立しています。サーバーが扱うメソッドは initialize、tools/list、tools/call、ping で、それ以外(notifications/initialized を含む)には -32601 のエラーが返ります。この応答は無視してかまいません。
ステップ7:tools/list
curl -X POST https://mcp.receiptroller.io/mcp \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "list_my_organizations",
"description": "List every ReceiptRoller organization the authorized user belongs to. ...",
"inputSchema": { "type": "object", "properties": {}, "additionalProperties": false },
"annotations": { "readOnlyHint": true, "idempotentHint": true }
},
{ "name": "list_my_stores", "...": "..." },
{ "name": "sales_get_kpis", "...": "..." }
]
}
}
一覧には、トークンのスコープに関係なくすべてのツールが含まれます。スコープが足りないツールは、呼んだときにエラーになります。
ステップ8:tools/call
curl -X POST https://mcp.receiptroller.io/mcp \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_my_organizations","arguments":{}}}'
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [ { "type": "text", "text": "1 organization(s)" } ],
"structuredContent": {
"organizations": [
{ "id": "{organizationId}", "name": "サンプル商店", "role": "SuperUser", "roles": ["SuperUser"] }
],
"page": 1,
"pageSize": 1000,
"totalCount": 1,
"totalPages": 1
}
}
}
データは structuredContent に入ります。ここで得た id を、ほかのツールの organizationId に渡します。たとえば KPI は次のように取得します。
curl -X POST https://mcp.receiptroller.io/mcp \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"sales_get_kpis","arguments":{"organizationId":"{organizationId}"}}}'
ステップ9:Node.js から呼び出す
アクセストークンを環境変数で渡すと、ツール一覧を表示し、list_my_organizations を呼び出すスクリプトです。追加のパッケージは不要です。
// mcp-check.mjs — node mcp-check.mjs
const MCP_URL = "https://mcp.receiptroller.io/mcp";
const accessToken = process.env.RR_ACCESS_TOKEN;
if (!accessToken) {
console.error("Set RR_ACCESS_TOKEN first.");
process.exit(1);
}
let nextId = 1;
async function rpc(method, params = {}) {
const response = await fetch(MCP_URL, {
method: "POST",
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ jsonrpc: "2.0", id: nextId++, method, params })
});
if (response.status === 401) {
throw new Error(`401: ${response.headers.get("www-authenticate")}`);
}
const body = await response.json();
if (body.error) {
throw new Error(`JSON-RPC ${body.error.code}: ${body.error.message}`);
}
return body.result;
}
const { tools } = await rpc("tools/list");
console.log(`${tools.length} tools:`);
for (const tool of tools) console.log(` ${tool.name}`);
const result = await rpc("tools/call", { name: "list_my_organizations", arguments: {} });
if (result.isError) {
console.error("Tool error:", result.content[0].text);
process.exit(1);
}
for (const org of result.structuredContent.organizations) {
console.log(`${org.id} ${org.name} (${org.roles.join(", ")})`);
}
RR_ACCESS_TOKEN={access_token} node mcp-check.mjs
よくあるエラー
401 と WWW-Authenticate
Authorization ヘッダーがないと、本文なしの 401 に次のヘッダーが付きます。
WWW-Authenticate: Bearer resource_metadata="https://mcp.receiptroller.io/.well-known/oauth-protected-resource/mcp"
MCP クライアントはこのURLからメタデータをたどって認可を始めます。トークンが期限切れ・取り消し済み・不正な場合は error="invalid_token" が加わります。リフレッシュトークンで更新するか、認可をやり直してください。
JSON-RPC のエラーとツールのエラー
| 返り方 | 意味と対処 |
|---|---|
HTTP 400、error.code が -32700 | 本文が JSON として読めません。 |
error.code が -32601 | サーバーが扱わないメソッドです。 |
result.isError が true、organizationId is required... | 先に list_my_organizations で ID を調べて渡してください。予約系は storeId も必要です(list_my_stores)。 |
result.isError が true、Upstream 403 | トークンに必要なスコープがないか、ユーザーがそのビジネスアカウント・店舗でその操作を許されていません。詳しくは 403/401が返る(権限・スコープ) を参照してください。 |
result.isError が true、Unknown tool: ... | ツール名が違います。tools/list の名前を使ってください。 |