MuseでAWS上のRemote MCP Server向けのCustom Connector作成を試す

MuseでAWS上のRemote MCP Server向けのCustom Connector作成を試す

Metaのパーソナルアシスタント「Muse」をAWSの会議室予約アプリと連携させ、DCR対応の認証ありと認証なしの2つのMCP接続方式を検証しました。認証ありはMuseにコネクターとして登録されて完全に統合される一方で、認証なしはコネクター画面に表示されないながらも、Muse内部のクライアントから直接ツール呼び出しが可能な違いが見えてきます。
2026.10.29

はじめに

これまで、MetaのパーソナルAIエージェント「Muse」とM5Stack Core2 for AWSをつなぎ、Muse Gadget SDKの導入とペアリング、Amazon Pollyによる日本語読み上げを試してきました。

https://dev.classmethod.jp/articles/shuntaka-muse-gadget-sdk-m5stack-core2/

https://dev.classmethod.jp/articles/shuntaka-muse-gadget-sdk-m5stack-core2-japanese-voice/

今回は、AWS上の会議室予約アプリをMuseのCustom Connectorから操作します。まずDCR対応・認証ありのRemote MCPでの接続を紹介し、次に認証なしのRemote MCPでMuseの認識や確認の流れがどう変わるかを見ます。

検証の結果、2つの接続先では、Museでの扱いが大きく違いました。

  • DCR対応・認証あり:Museにコネクターとして登録され、コネクター画面に表示されました。Cognitoにログインして接続し、検索・予約・キャンセルまで実行できました。ただし、トークンの更新に失敗すると401になり、再認証が必要です。コネクターの詳細画面では、ツールごとの許可は設定できなそうです。
  • 認証なし:コネクターとして認識されず、コネクター画面にも表示されませんでした。それでも、Museの中に作ったクライアントからネットワーク経由でツールが呼び出され、検索の結果が返りました。

今回の構成

同じ会議室予約ツールを、次の順に試します。DCR(Dynamic Client Registration)は、OAuthクライアントを動的に登録する仕組みです。

順序 接続先 Museとの接続
1 DCR対応・認証ありのRemote MCP FastMCP OAuth Proxyでクライアントを登録し、Cognitoでログイン
2 認証なしのRemote MCP 認証なしのAgentCore Gatewayへ直接接続

DCR対応・認証ありのRemote MCPを先に、認証なしのRemote MCPを後に試すAWS構成

FastMCP OAuth ProxyはAWS Lambda Web Functions上で動かします。MuseにはProxyが発行したトークンを渡し、ProxyからGatewayへはCognitoのアクセストークンで接続します。登録情報と暗号化したトークンはDynamoDB、鍵はSecrets Managerに保存します。

認証あり・なしで、予約処理用LambdaのIAMロールとDSQLスキーマ(booking_auth・booking_noauth)を分けています。

ツール 処理
search_available_rooms 日時・人数に合う空き会議室を探す
create_reservation 会議室を予約する
list_reservations 指定日の予約一覧を取得する
cancel_reservation 予約を取り消す

MuseのConnectorについて

2026年10月28日時点で確認した公式情報を整理します。

ConnectorとCustom Connector

ConnectorとCustom Connectorについては、公式ヘルプに説明があります。

MuseとMetaやサードパーティーのアプリ・サービスとの連携をConnectorと呼びます。カレンダーの確認やメールの作成など、Museから外部サービスを利用するための仕組みです。

一覧にないサービスについては、MuseにCustom Connectorの作成を依頼できます。公式ヘルプにも、サービスのAPI情報の取得などを含めてMuseが作成を案内すると記載されています。前回までのGadget SDKがデバイスとMuseをつなぐためのものだったのに対し、今回はMuseから外部サービスを利用するための連携を試します。

Connectorの審査と要件

Connectorの申請窓口として、Muse Connector Platformがあります。

申請したConnectorはMetaの審査を受け、承認後にMuse内で見つけられるようになります。一方、公式ヘルプでは、Custom ConnectorはMetaが審査しないと説明されています。

Connectorの要件は、公式ガイドラインにまとまっています。

予約サービスなら、検索から予約の確定までMuseで完了できることが求められます。途中でログインが必要になっても、そこから再開できるようにします。

MuseはWebサイトも閲覧できるため、リンクを返すだけでは不十分です。操作の手間を減らしたり、アカウントの情報を取得したりする利点が必要です。レポートの取得や在庫分析など、情報を得ることが目的なら読み取り専用でも構いません。

ツールの分類と許可

公式ガイドラインの3.3 Permission choicesでは、ツールごとの許可を次のように定めています。

ツールの分類 許可を求めるタイミング 選択肢
Read 初回。以降も「常に許可」を選ばなければ毎回 一度だけ許可・常に許可・拒否
Write, not sensitive 初回。以降も「常に許可」を選ばなければ毎回 一度だけ許可・常に許可・拒否
Write, sensitive 毎回 一度だけ許可・拒否

Custom Connectorでも同じ分類・許可画面になるかは未確認です。

MCPのツール注釈

MCPには、ツールの性質をクライアントに伝える注釈があります。使い方はMCP公式ブログで説明されています。

readOnlyHint: trueはデータなどを変更しないこと、destructiveHintは変更が破壊的かどうかを示します。いずれもヒントであり、Museの分類や許可画面との対応は未確認です。destructiveHintとMuseのSensitive writeも、同じ意味とは限りません。

今回のtools/listにannotationsは含まれていません。注釈による許可画面の違いは、本記事では検証しません。

MCPへの対応と接続・認証の公開情報

公式ガイドラインの5.4 Integration credentials and tool documentationでは、APIまたはMCPのドキュメントを提出するよう案内されています。接続先のエンドポイント、認証設定、必要なスコープなども提出対象で、認証情報の例としてOAuthクライアントの認証情報やAPIキーが挙げられています。

ガイドラインにはMCPへの言及がありますが、Custom Connectorで設定できる認証方式までは公開資料から確認できませんでした。今回Museの画面に表示された選択肢は、後述します。

今回確認した公開資料では、Streamable HTTPの正式な対応範囲や対応プロトコルバージョン、Remote MCPでのOAuthフロー・設定項目までは確認できませんでした。後述の検証では今回のGatewayとの通信を確認していますが、それだけでMuseの対応範囲全体が分かるわけではありません。

AWSにMCP接続先を用意する

検証用のmuse-mcp-server-testリポジトリにCDKで構成を定義し、東京リージョン(ap-northeast-1)へデプロイしました。スタック名はdev-muse-mcp-testです。ソースコードは次のリポジトリで公開しています。

https://github.com/shuntaka9576/muse-mcp-server-test

CDKはiac/awsでcdkコマンドを直接実行し、ビルドとDSQLの初期化・確認にはpackage.jsonのスクリプトを使います。各手順では、中で何をしているかも説明します。

Node.js 24、Bun 1.4.2、uv、Python 3、AWS CLI、jqを用意し、AWS認証済みのシェルで実行します。対象アカウントではCDK bootstrapを実施済みです。未実施の場合は、iac/awsでbunx cdk bootstrap 'aws://ACCOUNT_ID/ap-northeast-1'を先に実行します。

既存のスタックを削除する(作り直す場合)

前回の検証環境が残っている場合は、先に削除します。意図したアカウントであることを確認してから実行します。

aws sts get-caller-identity --query Account --output text
cd iac/aws
bunx cdk destroy
cd ../..

OAuth Proxyをパッケージする

bun install --frozen-lockfile
bun run proxy:build

proxy:build(tools/build-oauth-proxy.sh)は、Lambda Web Functionsに載せるZIPを作ります。Web FunctionsはNode.js 24で起動し、そこから同梱したPythonでFastMCPのサーバーを動かします。

  1. uvで、Linux ARM64向けのPython 3.13(cpython-3.13.8-linux-aarch64-gnu)を取得する
  2. apps/oauth-proxy/requirements.txtの依存パッケージ(FastMCP、py-key-valueのDynamoDB対応など)を、ARM64向けのwheelに限定し、ハッシュを検証してインストールする
  3. Python本体・依存パッケージ・server.py・proxy_app.py・index.mjsを.local/oauth-proxy-web.zipにまとめる

コードを変更した場合は、bun run type-check・bun run test・bun run lintでも確認します。

実行結果
Checked 186 installs across 273 packages (no changes) [43.00ms]
cpython-3.13.8-linux-aarch64-gnu is already installed
Using CPython 3.14.6
Resolved 82 packages in 363ms
Audited 82 packages in 2ms
Packaged 88 MiB

Using CPython 3.14.6は、uvがパッケージの解決に使った手元のPythonです。依存パッケージは--python-version 3.13と--python-platform aarch64-manylinux2014の指定により、Web Functionsで動かすPython 3.13向けに解決されます。ZIPは88MiBになりました。

差分を確認してデプロイする

cd iac/aws
bunx cdk diff
bunx cdk deploy --outputs-file ../../.local/outputs.json
cd ../..

作成される主なリソースは次のとおりです。

リソース 役割
Aurora DSQLクラスター 会議室と予約のデータ。スキーマはDSQLの初期化時に作成
Lambda関数(2つ) 予約ツール本体。booking_noauth用とbooking_auth用で、関数とIAMロールを分ける
AgentCore Gateway(2つ) MCPの接続先。受信認証はNONEとCUSTOM_JWT(Cognitoのアクセストークンを検証)
Cognitoユーザープール ログイン用。アプリクライアントはOAuth Proxy用とローカル検証用の2つ
Lambda Web Functions FastMCP OAuth Proxy。DCR・Cognitoログイン・Gatewayへの中継を担当
DynamoDB・Secrets Manager Proxyの登録情報・トークンの保存先と、署名・暗号化の鍵
SSM Parameter ProxyのURL・CognitoのクライアントID・GatewayのURL。Proxyが起動時に読む

diffではIAMの変更が一覧で表示されます。新規作成ではすべて追加(+)になり、Gateway用ロールの信頼ポリシーはaws:SourceAccountとaws:SourceArnで絞り込まれています。deployでも同じ一覧が表示され、承認すると作成が始まります。

ProxyのURLは、Web Functionsのエンドポイントを作るまで決まりません。CognitoのコールバックURL(<ProxyのURL>/auth/callback)はこのURLを使い、GatewayはProxy用クライアントのIDを検証し、ProxyはGatewayのURLを使います。これらをWeb Functionsの環境変数で渡すと、CloudFormationで循環参照になります。そこで3つの値をSSM Parameterに置き、Proxyが起動時に読み込むようにしました。デプロイは1回で完了します。

アカウントIDと、接続先URLのランダムな部分はxで伏せています。

実行結果
Stack includes security-sensitive updates and "--require-approval" is set to 'broadening'.
Do you wish to deploy these changes? (y/n) y
dev-muse-mcp-test: deploying... [1/1]

✅  dev-muse-mcp-test

✨  Deployment time: 128.17s

Outputs:
dev-muse-mcp-test.AuthenticatedGatewayUrl = https://dev-muse-mcp-authenticated-xxxxxxxxxx.gateway.bedrock-agentcore.ap-northeast-1.amazonaws.com/mcp
dev-muse-mcp-test.AuthenticatedToolFunctionName = dev-muse-mcp-booking_auth
dev-muse-mcp-test.AuthenticatedToolLogGroup = /aws/lambda/dev-muse-mcp-booking_auth
dev-muse-mcp-test.AuthenticatedToolRoleArn = arn:aws:iam::xxxxxxxxxxxx:role/dev-muse-mcp-test-AuthenticatedToolRoleEA46F3F5-wFjXGdQIPti7
dev-muse-mcp-test.AuthorizationEndpoint = https://dev-muse-mcp-xxxxxxxxxxxx-ap-northeast-1.auth.ap-northeast-1.amazoncognito.com/oauth2/authorize
dev-muse-mcp-test.CallbackUrls = http://localhost:8765/callback
dev-muse-mcp-test.ClientId = 6le7p2p3iqic2v770d0gqoso34
dev-muse-mcp-test.DatabaseClusterId = mvugcorh3pbautc2lemxuymvpi
dev-muse-mcp-test.DatabaseEndpoint = mvugcorh3pbautc2lemxuymvpi.dsql.ap-northeast-1.on.aws
dev-muse-mcp-test.DcrProxyBaseUrl = https://xxxxxxxxxxxxxxxx.j71.lambda-web.ap-northeast-1.on.aws
dev-muse-mcp-test.DcrProxyLogGroup = /aws/lambda-web/dev-muse-mcp-oauth-proxy
dev-muse-mcp-test.DcrProxyUrl = https://xxxxxxxxxxxxxxxx.j71.lambda-web.ap-northeast-1.on.aws/mcp
dev-muse-mcp-test.DiscoveryUrl = https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_zU6I29dAB/.well-known/openid-configuration
dev-muse-mcp-test.LocalCallbackUrl = http://localhost:8765/callback
dev-muse-mcp-test.ProxyClientId = 41tfoof6g1gtib50b6hj6q6gb2
dev-muse-mcp-test.PublicGatewayUrl = https://dev-muse-mcp-public-xxxxxxxxxx.gateway.bedrock-agentcore.ap-northeast-1.amazonaws.com/mcp
dev-muse-mcp-test.PublicToolFunctionName = dev-muse-mcp-booking_noauth
dev-muse-mcp-test.PublicToolLogGroup = /aws/lambda/dev-muse-mcp-booking_noauth
dev-muse-mcp-test.PublicToolRoleArn = arn:aws:iam::xxxxxxxxxxxx:role/dev-muse-mcp-test-PublicToolRoleDCB315D3-T14sYuds0pJG
dev-muse-mcp-test.Region = ap-northeast-1
dev-muse-mcp-test.Scope = dev-muse-mcp/invoke
dev-muse-mcp-test.TokenEndpoint = https://dev-muse-mcp-xxxxxxxxxxxx-ap-northeast-1.auth.ap-northeast-1.amazoncognito.com/oauth2/token
dev-muse-mcp-test.UserPoolId = ap-northeast-1_zU6I29dAB
Stack ARN:
arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:stack/dev-muse-mcp-test/af117f50-d32b-11f1-ab91-0e08a3827279

✨  Total time: 150.44s

接続先を確認する

デプロイの出力は.local/outputs.jsonに保存されます。Museとの接続に使う値を確認します。

jq '.["dev-muse-mcp-test"] | {DcrProxyUrl, PublicGatewayUrl, UserPoolId}' .local/outputs.json
実行結果
{
  "DcrProxyUrl": "https://xxxxxxxxxxxxxxxx.j71.lambda-web.ap-northeast-1.on.aws/mcp",
  "PublicGatewayUrl": "https://dev-muse-mcp-public-xxxxxxxxxx.gateway.bedrock-agentcore.ap-northeast-1.amazonaws.com/mcp",
  "UserPoolId": "ap-northeast-1_zU6I29dAB"
}
出力 用途
DcrProxyUrl DCR対応・認証ありのRemote MCPの接続先
PublicGatewayUrl 認証なしのRemote MCPの接続先
UserPoolId ログイン用ユーザーの作成先

ProxyとGatewayのURLは、スタックを作り直すたびに変わります。

ProxyがDCRに対応していることは、OAuthのメタデータで確認できます。Museも同じメタデータを読み、registration_endpointにクライアントを登録します。

proxy_base=$(jq -r '.["dev-muse-mcp-test"].DcrProxyBaseUrl' .local/outputs.json)
curl -s "$proxy_base/.well-known/oauth-authorization-server" \
  | jq '{issuer, registration_endpoint, code_challenge_methods_supported}'
実行結果
{
  "issuer": "https://xxxxxxxxxxxxxxxx.j71.lambda-web.ap-northeast-1.on.aws/",
  "registration_endpoint": "https://xxxxxxxxxxxxxxxx.j71.lambda-web.ap-northeast-1.on.aws/register",
  "code_challenge_methods_supported": [
    "S256"
  ]
}

registration_endpointがDCRの登録先です。PKCEはS256に対応しています。

DSQLを初期化する

bun run db:init --date 2026-10-29

db:init(tools/init-database.ts)は、実行者のIAM権限(dsql:DbConnectAdmin)でadminとして接続し、スキーマごとにテーブルとDBロールを作成します。AWS IAM GRANTでDBロールとLambdaのIAMロールを対応付けるため、各Lambdaはdsql:DbConnectのIAM認証で接続し、自分のスキーマだけを操作できます。

db:initが実行する主なSQL(booking_noauthの場合)
CREATE SCHEMA IF NOT EXISTS booking_noauth;
CREATE ROLE booking_noauth WITH LOGIN; -- 未作成の場合のみ
REVOKE ALL ON SCHEMA booking_noauth FROM PUBLIC;

CREATE TABLE IF NOT EXISTS booking_noauth.rooms (
  room_id text PRIMARY KEY, name text NOT NULL, capacity integer NOT NULL);
CREATE TABLE IF NOT EXISTS booking_noauth.reservations (
  reservation_id uuid PRIMARY KEY, room_id text NOT NULL,
  start_time timestamptz NOT NULL, end_time timestamptz NOT NULL,
  title text NOT NULL, attendees integer NOT NULL);
CREATE TABLE IF NOT EXISTS booking_noauth.booking_guards (
  room_id text PRIMARY KEY, version bigint NOT NULL);

GRANT USAGE ON SCHEMA booking_noauth TO booking_noauth;
GRANT SELECT ON booking_noauth.rooms TO booking_noauth;
GRANT SELECT, INSERT, DELETE ON booking_noauth.reservations TO booking_noauth;
GRANT SELECT, UPDATE ON booking_noauth.booking_guards TO booking_noauth;
AWS IAM GRANT booking_noauth TO '<PublicToolRoleArn>';

DSQLではDDLとDMLを同じトランザクションに含められないため、各DDLを単独で実行してから、初期データを1つのトランザクションで投入します。初期データはON CONFLICT DO NOTHINGで投入するので、再実行しても既存の予約は消えません。

booking_noauth・booking_authの両スキーマに、次の同じ初期データを投入しました。対象の時間帯は2026年10月29日14:00〜15:00(Asia/Tokyo)です。

会議室ID 会議室名 定員 対象の時間帯
room-sakura さくら 2人 空き。4人の検索では定員不足で候補外
room-kaede かえで 4人 空き
room-aoba あおば 8人 デモ用の既存予約あり
実行結果
booking_noauth: 初期化完了(既存予約を保持)
booking_auth: 初期化完了(既存予約を保持)
デモ検索: 2026-10-29 14:00–15:00 Asia/Tokyo、4人 → room-kaede

Museから接続する前に確認する

bun run db:verify --date 2026-10-29
bun run mcp:test --date 2026-10-29

db:verifyはMCPを通さずに両Lambdaを直接呼び出し、予約・一覧・キャンセルの結果をDSQLの行と照合します。mcp:testは認証なしのGatewayにStreamable HTTPで接続し、initialize・ツールの検出・同じ予約シナリオを実行します。どちらも、作成した予約を終了時にキャンセルします。

実行結果
booking_noauth: 予約・取消・同時予約・DSQL照合・権限分離 OK
booking_auth: 予約・取消・同時予約・DSQL照合・権限分離 OK
initialize / tools/list / 会議室予約・取消・同時予約テスト 成功
結果: .local/result-public.json

ログイン用のユーザーを作成する

Cognitoのセルフサインアップは無効にしているため、管理者APIでユーザーを作成します。パスワードはopenssl randで生成し、ポリシー(12文字以上、大文字・小文字・数字・記号)を必ず満たすよう先頭にAa1_を付けます。

pool_id=$(jq -r '.["dev-muse-mcp-test"].UserPoolId' .local/outputs.json)
password="Aa1_$(openssl rand -base64 24 | tr -d '/+=')"
aws cognito-idp admin-create-user --region ap-northeast-1 \
  --user-pool-id "$pool_id" --username muse-demo --message-action SUPPRESS
aws cognito-idp admin-set-user-password --region ap-northeast-1 \
  --user-pool-id "$pool_id" --username muse-demo --password "$password" --permanent
echo "username: muse-demo"
echo "password: $password"

--message-action SUPPRESSで招待メールを送らず、--permanentで初回ログイン時のパスワード変更を不要にします。

実行結果
{
    "User": {
        "Username": "muse-demo",
        "Attributes": [
            {
                "Name": "sub",
                "Value": "97b44ad8-6031-70a1-d15b-7be8cd8a61e2"
            }
        ],
        "UserCreateDate": "2026-10-29T09:05:28.359000+09:00",
        "UserLastModifiedDate": "2026-10-29T09:05:28.359000+09:00",
        "Enabled": true,
        "UserStatus": "FORCE_CHANGE_PASSWORD"
    }
}
username: muse-demo
password: Aa1_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

JSONはadmin-create-userの応答で、パスワードを設定する前の状態のためFORCE_CHANGE_PASSWORDと表示されます。続くadmin-set-user-password --permanentでCONFIRMEDになり、表示されたパスワードでそのままログインできます。パスワードは伏せています。

表示されたユーザー名とパスワードは、MuseからDCR対応の接続先につなぐときにCognitoのログイン画面で使うので、控えておきます。Cognitoから後でパスワードを確認することはできません。

認証ありの場合

DCR対応・認証ありの接続経路を赤枠で示した構成図

DCRに対応したFastMCP OAuth Proxyを接続先にすると、Museとの連携に成功しました。

接続前のコネクター画面です。この一覧に、作成したCustom Connectorが追加されることを確認します。Web版で撮影しましたが、設定項目はアプリでも同じです。

Custom Connectorを作る前のMuseのコネクター画面

Museに入力する接続プロンプト
Create a Custom Connector for my remote MCP server.

Name: custom.dev-muse-mcp-auth
Transport: Streamable HTTP
URL: https://lvqqpvh855ytndwp.j71.lambda-web.ap-northeast-1.on.aws/mcp
Authentication: OAuth 2.0 Authorization Code with PKCE (S256)
Client registration: Dynamic Client Registration (DCR)

Discover the OAuth metadata, register the client, and guide me through Cognito login.
After login, discover the available tools and test the connection using read-only tools.

接続の流れ

作成を依頼

iPhoneのMuseで新しいサイドチャットを作り、上のプロンプトを送信して開始しました。

Museに接続プロンプトを送信した画面

認証情報の保存

MuseはOAuthのメタデータを読み、DCRでの接続を始めました。応答の下に「セキュアな認証情報ストレージ」と「カスタムコネクターを追加」が表示されます。

セキュアな認証情報ストレージにカスタムコネクターを追加するボタンが表示された画面

Museの応答(全文)

Metadata discovered for the new endpoint — this is a new Lambda Web host, different from the previous 9z7w2jhnbkry7rw1... one:

Protected resource (/.well-known/oauth-protected-resource/mcp)

  • resource: https://lvqqpvh855ytndwp.j71.lambda-web.ap-northeast-1.on.aws/mcp
  • scopes_supported: dev-muse-mcp/invoke
  • bearer_methods_supported: header
  • Unauthenticated POST /mcp returns 401 with WWW-Authenticate pointing to that metadata — as expected

Authorization server (/.well-known/oauth-authorization-server, issuer https://lvqqpvh855ytndwp.j71.lambda-web.ap-northeast-1.on.aws/)

  • authorization: /authorize
  • token: /token — token_endpoint_auth_methods_supported: ["none"]
  • registration (DCR): /register
  • revocation: /revoke
  • grant types: authorization_code, refresh_token
  • code_challenge_methods: S256

I've started the connection for custom.dev-muse-mcp-auth using OAuth 2.0 Authorization Code with PKCE (S256) and Dynamic Client Registration, for read/write access to your remote MCP server's meeting-room tools, scoped to dev-muse-mcp/invoke with that /mcp URL as the resource.

You don't need to create an app or enter a client ID/secret — DCR registers the client automatically. Open the link below, sign in with Cognito when prompted, and authorize the dev-muse-mcp/invoke scope. Once you finish, I'll update the connector client/doc to this new endpoint, run list-tools, and test with the read-only tools.

カスタムコネクターを追加

「追加」を押すと、MetaがCustom Connectorを審査していないことの注意と、接続先のドメインが表示されました。

MetaがCustom Connectorを審査していないことの注意と接続先ドメインが表示された画面

ここで「追加」を押すと、MuseがProxyのドメインをサインインに使うことの確認がiOSから求められます。「続ける」を押しました。

MuseがProxyのドメインをサインインに使うことを確認するiOSのダイアログ

接続に同意

FastMCP OAuth Proxyの同意画面が開きました。MuseがProxyに接続しようとしていることと、認証情報の送り先(MuseのリダイレクトURI)が表示されます。

FastMCP OAuth Proxyの同意画面

「Advanced Details」を開くと、DCRで登録されたApplication ID(client_id)、リダイレクトURI、要求されたスコープを確認できます。「Allow Access」を押しました。

Advanced DetailsにApplication ID・リダイレクトURI・スコープが表示された同意画面

Cognitoにログイン

Cognitoのログイン画面に移ります。作成したmuse-demoユーザーでサインインしました。

Cognitoのログイン画面

Cognitoのログイン画面にユーザー名とパスワードを入力した状態

作成完了

ログイン後はMuseの画面に戻り、「追加済み」になってから接続の確認が続きました。

ログイン後にMuseの画面へ戻った状態

カスタムコネクターが追加済みになりMuseが処理を続けている画面

接続できたことと、ツールの検出・読み取りテストの結果が返りました。

接続完了とツール一覧が表示されたMuseの応答

読み取りテストの結果が表示されたMuseの応答

Museは「旧ホストと同じデモ用データベース」と説明していますが、スタックを作り直したため、DSQLクラスターは新しいものです。同じ初期データを投入したため、同じデータベースに見えたと考えられます。

コネクター画面で確認

接続後に、コネクター画面を確認しました。Custom Connectorの名前は、custom.dev-muse-mcp-authから「Dev Muse Mcp Auth」に変わって表示されています。

Web版では、「利用可能」タブには表示されず、「リンク済み」タブにだけ表示されました。

接続後のWeb版コネクター画面の利用可能タブ。Custom Connectorは表示されない

接続後のWeb版コネクター画面のリンク済みタブ。Dev Muse Mcp Authが表示される

アプリでは、「利用可能」と「リンク済み」の両方に表示されました。「利用可能」では一覧の下に「カスタム」が増え、「カスタムコネクター」を開くとDev Muse Mcp Authがあります。詳細画面には「バックグラウンド同期」と「リンクを解除」が表示されます。

接続後のアプリのコネクター画面。カスタムにカスタムコネクター1件が表示される

アプリのカスタムコネクター一覧にDev Muse Mcp Authが表示される

Dev Muse Mcp Authの詳細画面。バックグラウンド同期とリンクを解除が表示される

公式のコネクターの詳細画面と比べると、違いがはっきりします。マネーフォワード MEでは、「口座残高、保有銘柄の評価金額を読み取る」などの項目ごとに、「許可する」「リクエストする」「許可しない」を選べます。Instagramでも、「読み取りアクセス許可」と「書き込みと削除のアクセス許可」に分けて、項目ごとに設定できます。

マネーフォワード MEの詳細画面。項目ごとに許可する・リクエストする・許可しないを選べる

Instagramの詳細画面。読み取りアクセス許可の項目ごとに許可を設定できる

カスタムコネクターの詳細画面には、こうした項目がありません。予約の作成やキャンセルといった書き込み系のツールだけを止めるような、ツールごとの制御はできなそうです。

会議室を予約する

認証が切れたあと(後述)に再認証してから、次の順に依頼しました。各ステップの説明にあるツールの呼び出しは、予約用Lambda(booking_auth)のログで確認したものです。

空き会議室を探す

Museへの依頼
認証ありの custom.dev-muse-mcp-auth を使って、2026年10月29日14時から1時間、4人で使える空き会議室を探して。

空き会議室の検索結果。かえでの1室だけが表示される

Museはsearch_available_roomsを2回呼び、どちらも「かえで」だけが返っていました。日本時間の14時は、UTCの2026-10-29T05:00:00.000Zに直して渡されています。「あおば」には同じ時間帯に既存の予約があるため、候補に入りません。

予約する

Museへの依頼
その会議室を「設計レビュー」で予約して。

予約の結果。かえでに設計レビューの予約が作成される

Museはlist_reservationsで既存の予約を確認してからcreate_reservationを呼び、続けてもう一度list_reservationsを呼んでいました。「直後の予約一覧でも同内容で表示される」という説明は、この呼び出しと一致します。

予約一覧を見る

Museへの依頼
2026年10月29日の予約一覧を見せて。

予約一覧。あおばの既存予約とかえでの設計レビューの2件が表示される

list_reservationsは、この2件を返していました。

もう一度空き会議室を探す

Museへの依頼
同じ時間帯の空き会議室をもう一度探して。

再検索の結果。空き会議室はない

search_available_roomsは空のroomsを返していました。予約した「かえで」が候補から外れています。

予約をキャンセルする

Museへの依頼
今作った「設計レビュー」の予約をキャンセルして。

キャンセルの結果。設計レビューの予約が取り消される

Museはcancel_reservationのあとにlist_reservationsを呼んでおり、結果は「あおば」の1件だけでした。

キャンセル後に空き会議室を探す

Museへの依頼
同じ時間帯の空き会議室をもう一度探して。

キャンセル後の検索結果。かえでが空きに戻る

search_available_roomsは、再び「かえで」を返していました。

予約用Lambdaのログ(11:35〜11:38、抜粋)

ログからツール名・入力・結果を取り出しています。時刻は日本時間です。

11:35:09 {"tool":"search_available_rooms","input":{"start":"2026-10-29T05:00:00.000Z","end":"2026-10-29T06:00:00.000Z","attendees":4},"result":{"ok":true,"timezone":"Asia/Tokyo","rooms":[{"room_id":"room-kaede","name":"かえで","capacity":4}]}}
11:35:25 {"tool":"search_available_rooms","input":{"start":"2026-10-29T05:00:00.000Z","end":"2026-10-29T06:00:00.000Z","attendees":4},"result":{"ok":true,"timezone":"Asia/Tokyo","rooms":[{"room_id":"room-kaede","name":"かえで","capacity":4}]}}
11:36:11 {"tool":"list_reservations","input":{"date":"2026-10-29","start":"2026-10-28T15:00:00.000Z","end":"2026-10-29T15:00:00.000Z"},"result":{"ok":true,"date":"2026-10-29","timezone":"Asia/Tokyo","reservations":[{"reservation_id":"00000000-0000-4000-8000-202610290001","room_id":"room-aoba","start_time":"2026-10-29T05:00:00.000Z","end_time":"2026-10-29T06:00:00.000Z","title":"デモ用の既存予約","attendees":6,"room_name":"あおば"}]}}
11:36:18 {"tool":"create_reservation","input":{"start":"2026-10-29T05:00:00.000Z","end":"2026-10-29T06:00:00.000Z","attendees":4,"roomId":"room-kaede","title":"設計レビュー"},"result":{"ok":true,"reservation":{"reservation_id":"8523b810-7e80-4c32-b96b-e70b563f031b","room_id":"room-kaede","start_time":"2026-10-29T05:00:00.000Z","end_time":"2026-10-29T06:00:00.000Z","title":"設計レビュー","attendees":4,"room_name":"かえで"}}}
11:36:23 {"tool":"list_reservations","input":{"date":"2026-10-29","start":"2026-10-28T15:00:00.000Z","end":"2026-10-29T15:00:00.000Z"},"result":{"ok":true,"date":"2026-10-29","timezone":"Asia/Tokyo","reservations":[{"reservation_id":"00000000-0000-4000-8000-202610290001","room_id":"room-aoba","start_time":"2026-10-29T05:00:00.000Z","end_time":"2026-10-29T06:00:00.000Z","title":"デモ用の既存予約","attendees":6,"room_name":"あおば"},{"reservation_id":"8523b810-7e80-4c32-b96b-e70b563f031b","room_id":"room-kaede","start_time":"2026-10-29T05:00:00.000Z","end_time":"2026-10-29T06:00:00.000Z","title":"設計レビュー","attendees":4,"room_name":"かえで"}]}}
11:37:03 {"tool":"list_reservations","input":{"date":"2026-10-29","start":"2026-10-28T15:00:00.000Z","end":"2026-10-29T15:00:00.000Z"},"result":{"ok":true,"date":"2026-10-29","timezone":"Asia/Tokyo","reservations":[{"reservation_id":"00000000-0000-4000-8000-202610290001","room_id":"room-aoba","start_time":"2026-10-29T05:00:00.000Z","end_time":"2026-10-29T06:00:00.000Z","title":"デモ用の既存予約","attendees":6,"room_name":"あおば"},{"reservation_id":"8523b810-7e80-4c32-b96b-e70b563f031b","room_id":"room-kaede","start_time":"2026-10-29T05:00:00.000Z","end_time":"2026-10-29T06:00:00.000Z","title":"設計レビュー","attendees":4,"room_name":"かえで"}]}}
11:37:24 {"tool":"search_available_rooms","input":{"start":"2026-10-29T05:00:00.000Z","end":"2026-10-29T06:00:00.000Z","attendees":4},"result":{"ok":true,"timezone":"Asia/Tokyo","rooms":[]}}
11:38:03 {"tool":"cancel_reservation","input":{"reservationId":"8523b810-7e80-4c32-b96b-e70b563f031b"},"result":{"ok":true,"cancelled":true,"reservation_id":"8523b810-7e80-4c32-b96b-e70b563f031b"}}
11:38:07 {"tool":"list_reservations","input":{"date":"2026-10-29","start":"2026-10-28T15:00:00.000Z","end":"2026-10-29T15:00:00.000Z"},"result":{"ok":true,"date":"2026-10-29","timezone":"Asia/Tokyo","reservations":[{"reservation_id":"00000000-0000-4000-8000-202610290001","room_id":"room-aoba","start_time":"2026-10-29T05:00:00.000Z","end_time":"2026-10-29T06:00:00.000Z","title":"デモ用の既存予約","attendees":6,"room_name":"あおば"}]}}
11:38:36 {"tool":"search_available_rooms","input":{"start":"2026-10-29T05:00:00.000Z","end":"2026-10-29T06:00:00.000Z","attendees":4},"result":{"ok":true,"timezone":"Asia/Tokyo","rooms":[{"room_id":"room-kaede","name":"かえで","capacity":4}]}}

認証が切れたときの挙動

接続してから時間を空けてMuseに依頼すると、ツールの呼び出しが401になり、再認証が必要になりました。今回の検証では2回起き、原因はそれぞれ違いました。

トークンは2段になっています。

区間 アクセストークン リフレッシュトークン
MuseとProxyの間 15分(Cognitoと同じ値) FastMCPが発行する。使うたびに新しいものに入れ替わり、古いものは無効になる
ProxyとCognitoの間 15分 1時間(refreshTokenValidity)。使っても入れ替わらず、期限も延びない

正常に更新できるときは、次の流れになります。Museが更新を依頼するたびに、ProxyはCognitoのトークンも更新し、Museのリフレッシュトークンを新しいものに入れ替えます。

経過は次のとおりです。

  • 09:15 Cognitoにログインして接続
  • 09:33 401(1回目)。10:06にも同じ理由で401
  • 10:08ごろ 再認証(10:09にツールを呼べていた)
  • 11:33 401(2回目)
  • 11:34ごろ 再認証。このあとに、前述の会議室の予約を実施

以下はOAuth ProxyのCloudWatch Logs(/aws/lambda-web/dev-muse-mcp-oauth-proxy)の抜粋です。時刻は日本時間に直し、折り返された行は1行にまとめています。

1回目:入れ替え済みのリフレッシュトークンが送られる

最初の接続から約18分後の09:33に、Museがアクセストークンを更新しようとしました。Proxyは09:20に停止していたため、この更新でコールドスタートが起きています。

Proxyのログ(09:20〜09:35、抜粋)
09:20:59 platform.spinDownStart
09:33:27 platform.initStart
09:33:32 platform.initReport (totalDurationMs: 5016)
09:33:32 INFO     Issued new FastMCP tokens (rotated refresh) for client=abb4aa9f-97f4-48bb-a2e2-a21c33468577 (access_jti=APcqdDQZ, refresh_jti=3O3K9b5m)
09:33:37 WARNING  Refresh token not found for client=abb4aa9f-97f4-48bb-a2e2-a21c33468577 (token_hash=46a759ba); it was already rotated, expired, or revoked. Rejecting with invalid_grant, which forces the client to re-authenticate.
09:33:38 INFO     Auth error returned: invalid_token (status=401)
09:33:51 INFO     Auth error returned: invalid_token (status=401)
09:35:28 WARNING  Refresh token not found for client=abb4aa9f-97f4-48bb-a2e2-a21c33468577 (token_hash=46a759ba); it was already rotated, expired, or revoked. Rejecting with invalid_grant, which forces the client to re-authenticate.
09:35:28 INFO     Auth error returned: invalid_token (status=401)

ログの各行は、次のことを示しています。

  • Issued new FastMCP tokens (rotated refresh):更新に成功し、新しいリフレッシュトークンを発行して、古いものを無効にした
  • Refresh token not found ... already rotated:届いたリフレッシュトークンが無効なので、invalid_grantで拒否した
  • Auth error returned: invalid_token (status=401):期限切れのアクセストークンで/mcpを呼んだリクエストを拒否した

更新に成功した5秒後に、同じクライアントから無効なリフレッシュトークンが届いています。コールドスタートで応答まで約5秒かかったため、Museは応答を受け取れないまま、古いトークンで再試行したと考えられます(Muse側のログは見られないので推測です)。10:06にも同じtoken_hash=46a759baが届いており、Museは09:33:32に発行された新しいリフレッシュトークンを保存していないようです。

FastMCP 4.1.4のリフレッシュトークンは1回しか使えず、入れ替え済みのトークンを受け付ける設定もありません。一度こうなると、再認証するしかありません。

2回目:Cognitoのリフレッシュトークンが期限切れになる

10:08ごろに再認証し、10:09にはツールを呼べていました。その後1時間以上空けて11:33に依頼すると、また401になりました。

Proxyのログ(10:14〜11:33、抜粋)
10:14:11 platform.spinDownStart
11:33:01 platform.initStart
11:33:05 platform.initReport (totalDurationMs: 3942)
11:33:06 WARNING  Upstream OAuth token refresh failed: invalid_grant
11:33:22 WARNING  Upstream OAuth token refresh failed: invalid_grant
11:33:23 INFO     Auth error returned: invalid_token (status=401)

今回はRefresh token not foundは出ていません。Museから届いたリフレッシュトークンは有効で、ProxyはCognitoにトークンの更新を依頼しています。Upstream OAuth token refresh failed: invalid_grantは、その依頼をCognitoが拒否したことを示します。

Cognitoのリフレッシュトークンは1時間で期限が切れる設定です。10:08ごろに発行されたものは、11:08ごろに切れていました。使っても期限は延びないため、ログインから1時間を過ぎると、Museが更新を依頼しても必ず失敗します。

再認証の方法

401になったあと、Museに「解除して再設定して」と依頼しました。Museは、自分では接続を解除できないと答えました。そのうえで、Settings > Connectorsで「Dev Muse MCP Auth」をDisconnectしてから、リンクで再設定する手順を示しました。

応答の下に表示されるボタンは、モバイルとWeb版で違いました。

モバイルでは、「セキュアな認証情報ストレージ」が「追加済み」のままで、押せませんでした。ここからは再認証できないため、コネクターの詳細画面にある「リンクを解除」で接続を解除してから、接続し直す必要がありそうです。

モバイルでの応答。セキュアな認証情報ストレージは追加済みのまま押せない

Web版では、同じ応答の下に「Dev Muse Mcp Auth」の「連携する」ボタンが表示されました。接続を解除しなくても、ここから再認証できます。

Web版での同じ応答。Dev Muse Mcp Authの連携するボタンが押せる状態で表示される

対策の候補

今回は設定を変えずに再認証しました。次の変更で起きにくくなるはずですが、試してはいません。

  • 2回目への対策:Proxy用のCognitoクライアントのrefreshTokenValidityを延ばす。今回はmcp-test-stack.tsで1時間に短くしている。指定しなければ、Cognitoの既定値の30日になる
  • 1回目への対策:OIDCProxyのfastmcp_access_token_expiry_secondsで、Museに渡すアクセストークンを長くする。Museが更新を依頼する回数が減り、Cognitoのアクセストークンの更新はProxyが裏で行う。ただし、Cognitoのリフレッシュトークンが切れれば失敗するので、上の対策と組み合わせる必要がある

認証なしの場合

認証なしの接続経路を赤枠で示した構成図

次は認証なしの接続先です。

始める前に、DCR側のコネクターの接続を解除しておきます。両方つないだままでも問題はありませんが、どちらも同じ4ツールを公開しているので、両方のツールが呼ばれないようにするためです。アプリのコネクター画面で「Dev Muse Mcp Auth」の詳細を開き、「リンクを解除」を押します。

Dev Muse Mcp Authの詳細画面。リンクを解除で接続を解除する

iPhoneのMuseで、次のプロンプトを入力します。

Museに入力する接続プロンプト
Create a Custom Connector for my remote MCP server.

Transport: Streamable HTTP
URL: https://dev-muse-mcp-public-hm7plx0a2f.gateway.bedrock-agentcore.ap-northeast-1.amazonaws.com/mcp

Please discover the available tools and test the connection.

接続の流れ

作成を依頼

iPhoneのMuseで新しいサイドチャットを作り、上のプロンプトを送信して開始しました。Museは「Creating connector」と表示して作業を始めます。

初めて接続するときは、ここで接続先への許可画面が表示され、「一度だけ許可」「このサイトで常に許可」「許可しない」から選びます。今回は以前の検証ですでに許可していたため、許可画面は表示されませんでした。

Museに認証なしの接続プロンプトを送信した画面

作成完了

Museは新しく作らず、以前の検証で作ったcustom.dev-muse-mcpの接続先を、新しいGatewayのURLに更新しました。名前は認証ありの場合と同じ形ですが、後述のとおり、これはコネクターではありません。ツールの検出と読み取りテストの結果が返りました。

認証なしの接続先でツールの検出と読み取りテストが終わったMuseの応答

Museの応答(全文)

Done — this is the same server behind a new gateway, so I updated the existing custom.dev-muse-mcp instead of creating a duplicate.

New endpoint
https://dev-muse-mcp-public-hm7plx0a2f.gateway.bedrock-agentcore.ap-northeast-1.amazonaws.com/mcp

  • Replaces: dev-muse-mcp-public-hqcflgspkq (which replaced pne4qzygrj)
  • Updated: bin/client.py ENDPOINT and CONNECTOR.md (host + Verified section)

Discovery — probe

  • initialize: protocol 2025-11-25, serverInfo dev-muse-mcp-public / 1.0.0, instructions identical to before
  • tools/list: same 4 tools, no nextCursor
  1. booking___search_available_rooms — read-only: start_time, end_time, attendees
  2. booking___list_reservations — read-only: date
  3. booking___create_reservation — write: room_id, start_time, end_time, attendees, title
  4. booking___cancel_reservation — destructive write: reservation_id

Test via the client

  • list-tools: 4 tools returned, exit 0
  • booking___list_reservations for 2026-10-29: ok: true — same pre-existing demo booking, room-aoba (あおば), 05:00–06:00Z, “デモ用の既存予約”
  • booking___search_available_rooms for 2026-10-30 14:00–15:00, 4 attendees: ok: true — room-kaede (かえで, capacity 4) and room-aoba (あおば, capacity 8)

So the new gateway fronts the same demo database. No write tools were called.

Registration note, separate from whether it works: the tools work, but as before this public, no-auth endpoint has no hosted connection record — it runs as the file-based client + network access under the custom.dev-muse-mcp name you use for it.

読み取りテストの2つの呼び出しは、認証なし側の予約用Lambda(booking_noauth)のログにも残っていました(11:57:16のlist_reservations、11:57:19のsearch_available_rooms)。DCRのときと同じく「同じデモ用データベース」と説明していますが、DSQLクラスターは作り直した新しいものです。

応答の最後には、認証なしの接続先には「hosted connection record」(Museに登録された接続の記録)がないと書かれています。認証ありの場合のようにコネクターとしてMuseに登録するのではなく、Museの中に作ったクライアント(bin/client.py)から、ネットワーク経由で呼び出している、という説明です。

コネクター画面で確認

接続後に、アプリのコネクター画面の「リンク済み」タブを確認しました。DCRのときと違い、custom.dev-muse-mcpは表示されません。DCR側は解除済みなので、カスタムコネクターは1件もありません。Museはこの接続先を、コネクターとして認識していません。応答の「hosted connection record がない」という説明と一致します。

接続後のアプリのコネクター画面のリンク済みタブ。カスタムコネクターは表示されない

空き会議室を探す

認証なしでは、空き会議室の検索だけを試します。

Museへの依頼
認証なしの custom.dev-muse-mcp を使って、2026年10月29日14時から1時間、4人で使える空き会議室を探して。

認証なしの接続先での空き会議室の検索結果。かえでの1室だけが表示される

booking_noauthのログでは、12:30:35にsearch_available_roomsが1回呼ばれ、「かえで」だけが返っていました。ツールに渡された条件は開始2026-10-29T05:00:00.000Z、終了2026-10-29T06:00:00.000Z、人数4で、DCRで検索したときと同じです。「あおば」が返らないのは、同じ時間帯に既存の予約があるためです。

booking_noauthのログ(抜粋)
12:30:35 {"tool":"search_available_rooms","input":{"start":"2026-10-29T05:00:00.000Z","end":"2026-10-29T06:00:00.000Z","attendees":4},"result":{"ok":true,"timezone":"Asia/Tokyo","rooms":[{"room_id":"room-kaede","name":"かえで","capacity":4}]}}

さいごに

DCR対応・認証ありのRemote MCPは、Museにコネクターとして登録され、検索・予約・キャンセルまで実行できました。ただし、トークンの更新に失敗すると再認証が必要です。認証なしのRemote MCPは、コネクターとして認識されないまま、Museの中に作ったクライアントからツールが呼び出されました。

この記事をシェアする

関連記事