PositionGuard について
PositionGuard は、グループメンバーが指定エリアにいるかを AI アシスタントが確認する MCP サーバーです。本人が共有に同意した情報だけを返し、座標は取得せず、不明や非開示の状態は unknown とします。
主な機能
- 01現在地と最終確認エリアの照会
- 02グループ名とエリア名の一覧
- 03開示済みメンバーと人数の確認
- 04メンバー単位・エリア単位の共有設定を尊重
- 05座標や履歴を扱わない読み取り専用
活用シーン
- 01メンバーが自宅や学校にいるか尋ねる
- 02エリアにいる開示済みメンバーを確認
- 03古い位置情報を考慮して人数を数える
MCP 対応
`list_groups`、`list_areas`、`where_is_member`、`who_is_at_area`、`count_members_at_area` などの読み取り専用ツールを提供します。Streamable HTTP、SSE、stdio に対応します。
設定
リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。
https://mcp.positionguardai.com/mcpclaude mcp add --transport http com-positionguardai-positionguard https://mcp.positionguardai.com/mcp{"mcpServers":{"com-positionguardai-positionguard":{"type":"http","url":"https://mcp.positionguardai.com/mcp"}}}Cursor で開く{"servers":{"com-positionguardai-positionguard":{"type":"http","url":"https://mcp.positionguardai.com/mcp"}}}VS Code で開く{"mcpServers":{"com-positionguardai-positionguard":{"type":"http","url":"https://mcp.positionguardai.com/mcp"}}}{"mcpServers":{"com-positionguardai-positionguard":{"serverUrl":"https://mcp.positionguardai.com/mcp"}}}コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。
ツール
詳細
positionguard-mcp
AI アシスタントが PositionGuard の所在について質問できる MCP サーバーです。Earl はスケートパークにいるか、家にいるのは誰か、クラブには何人いるかといった質問に答えます。各メンバーがアシスタントとの共有に同意した情報だけを回答し、同意していない場合は unknown と伝えます。
このサーバーは、公開 REST API を利用する決定論的なクライアントです。API キーを 1 つ保持し、読み取り専用の 3 つのエンドポイントを呼び出して、返された情報を小規模で明示的な語彙にマッピングします。内部にモデルはなく、特別なアクセス権もありません。すべての保証は api.positionguardai.com で適用され、そこでキーの検証と同意の確認が行われます。このサーバーが API の返す情報を増やすことはできず、質問することしかできません。
取得する情報と、取得しない情報
このサーバーは、意図的にアシスタントへ必要最低限の情報だけを提供します。取得する情報はすべて確認できます。
- スコープは 4 つではなく 3 つです。
presence:read、counts:read、groups:readを許可してください。areas:readは許可しないでください。このスコープを許可すると、エリアの中心座標を返す/groups/{id}/areasを利用できるようになりますが、このサーバーはこのエンドポイントを呼び出しません。エリア名と ID は、設計上座標を含まない/area-countsから取得します。将来の変更で/areasの取得が指定された場合でも、クライアントは取得を拒否し、その拒否をテストで確認します。 - Integration キーではなく Assistant キーを使用します。 開発者ポータルでタイプが Assistant のキーを作成してください。メンバーが Let AI agents see whether I'm at an area を有効にしている場合に限り、Assistant キーでそのメンバーを確認できます。有効にしていない場合、キーに返される値は unknown です。Integration キーも仕組み上は使用できますが、この同意確認を回避します。それを選ぶかどうかはキーの所有者が決めることです。このリファレンス設定では、その使用例を示していません。
- 本文、キー、名前はログに記録しません。 ログ行に含まれるのは、パス、HTTP ステータス、項目数、処理時間、ツール名、ツールが返した
statusです。キーが表示されるのは起動時の 1 回だけで、先頭の 16 文字が表示されます。これはポータルに表示されるものと同じプレフィックスです。 unknownは「不在」ではありません。 オプトインしていないメンバー、グローバル Ghost モードのメンバー、共有を一時停止したメンバー、しばらく位置情報を送信していないメンバーは、いずれも通信上は同じように見え、すべてunknownにマッピングされます。最初の 3 つのケースはtest/fixtures/に収録され、同一の扱いになることがテストで確認されます。エリアごとの情報も同様です。エリアにいるメンバーがそのエリアをアシスタントに対して非表示にしている場合、そのメンバーはそのエリアの一覧に表示されません。この不在もunknownであり、「そこにいない」という意味ではありません。Ghost モードで公開グループに参加したメンバーは名簿に含まれず、匿名で参加したメンバーは「Anonymous」として表示されます。これらの処理は、このサーバーが名簿を受け取る前に API が行います。ツールの説明では、unknownは分からないことを意味するとモデルに明確に伝えます。古い最終確認位置が現在の場所として返されることはありません。unknown、または経過時間を添えたlast_known_at_areaとして返されます(後述)。カウントには古い情報のメンバーも含まれ、その旨が示されます。- キャッシュは行いません。 API は 15 秒間キャッシュします。キャッシュをもう 1 つ設けると、同意が撤回された後も情報が古いまま残る場所が増えます。
- 読み取り専用です。 書き込みツール、座標、メッセージ、履歴はありません。すべてのツールに
readOnlyHintが付与されています。
ツール
すべての回答は status フィールドを含む JSON です。ok は質問に回答できたことを意味します。unknown は回答できなかったことを意味し、reason に理由が 1 語で示されます。API エラーは、HTTP ステータスを 1 文で示す MCP ツールエラーとして返され、再試行は行いません。
| ツール | スコープ | 応答 |
|---|---|---|
list_groups | groups:read | {status, groups: [{group_id, name, group_type}]} |
list_areas | counts:read | {status, areas: [{area_id, name, group_id, group_name}]} — 名前のみ。カウントやジオメトリは含みません |
where_is_member | presence:read | {status: "at_area", area, inside_areas?, since?} | {status: "last_known_at_area", area, inside_areas?, since?, last_confirmed_seconds_ago, note} | {status: "not_at_area"} | {status: "unknown", reason} |
who_is_at_area | presence:read | {status, area, confirmed: [nickname…], last_known: [{nickname, last_confirmed_seconds_ago}…], members, last_known_note?, undisclosed_note?, count_note?} — 開示されたメンバーのみ |
count_members_at_area | counts:read | {status, member_count, stale_count, undisclosed_count, note} |
where_is_member は、1つのグループ内またはすべてのグループを対象に、ニックネームを大文字小文字を区別せずに検索します。reason は not_disclosed、stale、no_such_member のいずれかです(これはエラーではなく応答なので、モデルが結果をでっち上げることはありません)。who_is_at_area は、サーバーがそのエリアにいるメンバーを応答にリストされた人数より多くカウントしている場合に、count_note(「このエリアで N 人をカウント。M 人をリスト」)を追加します。このとき N は member_count + undisclosed_count です。公開グループでは、Ghost 経由で参加したメンバーの行は返されず、メンバー上限を超えると呼び出し元自身以外の行も返されません。また、メンバーがそのエリアにいてもアシスタントには開示されない場合があります。そのため、カウントが異なることを示している場合に、リストが完全なものとして表示されることはありません。メンバーが非開示の場合、注記にはその人数が示され、「名前付きリストを完全なものとして報告しないでください」と記されます。undisclosed_count または stale_count がゼロ以外の場合、member_count は最低人数を示し、note には「少なくとも N 人」として報告するよう記されています。API がカウントをまったく提供しない場合(アーカイブ済みエリア)、応答は unknown、理由は count_unavailable となり、ゼロにはなりません。
エリア内のエリア。 グループには都市内のホームを設定できます。API は、メンバーが属しているグループ内のすべてのエリアを、内側から順に (inside_areas) リストします。where_is_member は最も内側のエリアを area として、その横に全エリアのリストを添えて応答するため、「Fred はロサンゼルスにいますか」という質問にもリストから回答できます。who_is_at_area は、質問されたエリアとこのリストを照合するため、自宅にいる人も都市にいる人として名前が挙がります。メンバーはエリアごとにアシスタントに見せる情報を選択できます。Home は共有し、Los Angeles は共有しない人の場合、Home のみのリストが返され、省略を示すものは何もありません。したがって、inside_areas にエリアがないことは、メンバーがそのエリアの外にいる証拠にはなりません — ツールの説明にもその旨が記されています — そして、情報が伏せられていることを示す唯一の手がかりは、都市の undisclosed_count です。これを読み取るのが count_note です。since が返されるのは、名前が挙がったエリアが API の入域時刻計測対象である場合のみです。inside_areas を返さないサーバーでは、従来どおり単一の current_area が回答に使われます。
鮮度と最終確認時の応答。 PositionGuard サーバーは、メンバーの電話が無応答の間、最後に確認されたエリアにメンバーがいるものとして保持し、該当する行に position_fresh: false と位置情報の経過時間を設定できます。where_is_member はこれらに対し、last_confirmed_seconds_ago と注記(「Lake House で最後に確認されたのは 10 h 前です。より新しい位置情報はありません」)を含む last_known_at_area として応答し、at_area とはしません。who_is_at_area は該当するメンバーを confirmed ではなく last_known にリストし、count_members_at_area の注記には、カウントのうち最近確認されていない人数が示されます。ツールの説明には、モデルが最終確認時の回答を、その経過時間とともに伝え、現在その場所にいるとは決して伝えないよう記されています。since が示すのは常にメンバーがエリアに入った時刻であり、最後に確認された時刻ではありません。position_fresh を返さないサーバーでは、応答は 0.1.0 のときと同じです。
ワイヤ形式から status へのマッピングは src/core/mapping.ts に記述されており、モデルがサーバーを認識する前に test/fixtures/ のキャプチャ済み応答を使ってテストされます。
設定
| 環境変数 | 必須 | デフォルト |
|---|---|---|
POSITIONGUARD_API_KEY | はい | — pg_live_ で始まる必要があります。それ以外の場合、サーバーは起動を拒否します |
POSITIONGUARD_API_BASE | いいえ | https://api.positionguardai.com/api/v1 |
TRANSPORT | いいえ | http(ストリーム可能な HTTP + SSE)または stdio |
PORT | いいえ | 8788(HTTP トランスポート) |
HOST | いいえ | 0.0.0.0(HTTP トランスポート)。コンテナー外では 127.0.0.1 を使用してください |
起動時に、サーバーは GET /groups を一度呼び出します。401 または 403 が返された場合は、修正方法を示すメッセージを表示して終了します。グループリストが空の場合は、警告を表示して起動します。ツールを呼び出すたびにキーを再確認するため、起動時に API に一時的に到達できなくても、ログに記録されるだけで致命的なエラーにはなりません。
トランスポート
コードパスは 1 つで、入口は 3 通りです。このバージョンの HTTP トランスポートには認証がありません。サーバーはプライベートな Docker ネットワークまたは localhost で実行し、外部に公開しないでください。
| パス | トランスポート | 対象 |
|---|---|---|
/mcp | ストリーム可能な HTTP、ステートレス | Open WebUI |
/sse(および /messages) | SSE | SSE のみ対応する Home Assistant の mcp 統合 |
stdio(TRANSPORT=stdio) | stdio | Claude Desktop などのランチャー |
/healthz | — | Docker のヘルスチェック。{"ok":true} を返します |
セットアップ
各セットアップには、テスト済みまたは未テストのマークが付いています。テスト済みのセットアップについてのみ、このサーバーで実際に確認された動作を説明しています。
1. コンテナー上の Open WebUI + Anthropic + このサーバー — テスト済み
docker-compose.example.yml は、サーバーのポートを公開せずに、Open WebUI とこのサーバーを同じプライベートネットワーク上で実行します。WEBUI_SECRET_KEY と POSITIONGUARD_API_KEY を .env ファイル(Git の追跡対象外)に設定してから、次を実行します。
cp docker-compose.example.yml docker-compose.yml
docker compose up -d
Open WebUI で、管理 → 設定 → 接続を開き、ベース URL を https://api.anthropic.com/v1、Anthropic のキーを指定して、OpenAI 互換の接続を追加します。管理 → 設定 → 外部ツールを開き、認証をなしに設定して、http://positionguard-mcp:8788/mcp にストリーム可能な HTTP タイプの MCP サーバーを追加します(キーを空にした Bearer は空のヘッダーを送信するため、サーバーに拒否されます)。Open WebUI は 0.6.31 以降、ストリーム可能な HTTP MCP サーバーをネイティブでサポートしています。
このセットアップおよび他のクラウドモデルを使用するすべてのセットアップに関する注意:開示するよう選択したプレゼンス情報は、回答を生成するためにモデルプロバイダーへ送信されます。
Assistant キーを使って、本番 API で検証済みです。 設定を有効にしたメンバーについては、そのエリアが返されます。同じメンバーでも設定が無効の場合は、「不在」ではなく不明として返されます。 未開示または古いメンバーを含む人数は、最低数として報告されます。
2. Claude Desktop、stdio — テスト済み
一度ビルドし(npm install && npm run build)、次に Claude Desktop の claude_desktop_config.json に追加します。
{
"mcpServers": {
"positionguard": {
"command": "node",
"args": ["/absolute/path/to/positionguard-mcp/dist/index.js"],
"env": {
"POSITIONGUARD_API_KEY": "pg_live_…",
"TRANSPORT": "stdio"
}
}
}
}
パッケージが公開されたら、"command": "npx", "args": ["-y", "positionguard-mcp"] に置き換えられます。
ログは stderr に、プロトコルは stdout に出力されます。
3. Home Assistant Assist + mcp 統合 + このサーバー — 未テスト
Home Assistant から到達できる HTTP トランスポートでサーバーを実行します。
Model Context Protocol 統合を追加し、SSE URL http://<host>:8788/sse を指定します。ツールは会話エージェントの下に表示されます。Assist で使用するエージェント(Anthropic、OpenAI、Ollama など)に対して、ツールを有効にしてください。Home Assistant のクライアントは SSE のみ対応しており、認証には OAuth または認証なしのいずれかしか選べません。そのため、このサーバーの認証なし SSE エンドポイントを、管理下のネットワーク上でそのまま使用します。
4. Home Assistant Assist + Ollama + このサーバー、完全ローカル — 未テスト
3 と同じですが、会話エージェントとして Ollama を使用します。プレゼンス情報がネットワーク外に送信されることはありません。
5. Open WebUI + Ollama、完全ローカル — 未テスト
1 と同じですが、モデル接続には Anthropic の代わりに Ollama を使用します。プレゼンス情報がネットワーク外に送信されることはありません。
レート制限とエラー
API はキーごとにレート制限を適用します。429 が返された場合、Retry-After に基づく待ち時間を示すツールエラーとしてモデルに返され、サーバーは再試行しません。他のすべての 2xx 以外のステータスも同様です。1 回リクエストし、1 つの回答またはエラーを返すだけで、その間に別の処理は行いません。
開発
npm install
npm test # builds, then unit + end-to-end tests over all three transports
npm run dev # runs from source (tsx)
src/core/ には、ツール、wire からステータスへのマッピング、API クライアントが含まれています。
Node 固有のものは何もインポートせず、fetch と AbortSignal のみを使用します。この条件はテストで検証されているため、ホスト版が必要な場合は、同じコアを SDK のウェブ標準トランスポートとともに Cloudflare Worker で実行できます。
src/transports/ は Node 側で、ストリーム可能な HTTP、SSE、stdio を扱います。
test/fixtures/ には、wire の各状態に対応するレスポンスが 1 つずつ含まれています(一覧と出典は test/fixtures/README.md に記載されています)。npm run capture -- <group_id> を実行すると、ライブ API から取得した機密情報を除去済みのレスポンスが出力され、フィクスチャを更新できます。合成テストグループを使用してください。
なぜアプリもオープンソースではないのですか?
アクセスを仲介するコンポーネントは監査可能です。このサーバー、Home Assistant 統合、Hubitat アプリのコードを確認し、それぞれが何を要求するのか、要求可能な範囲より少ない情報だけを求めているかを検証できます。ホスト型サービスはオープンではありません。中核となるエンジン――長年にわたる変化への対応、デバウンス、古さの区別、クラスタリング、可視性レイヤー――こそが製品であり、誠実に回答を拒否できる仕組みでもあるからです。推測するシステムは「わかりません」と言えません。常にそう言うことになってしまいます。REST API を MCP でラップするのは週末でできます。状態が頻繁に変動しない在室検知は、そうはいきません。
ライセンス
MIT。LICENSE を参照してください。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。