PositionGuard

com.positionguardai由 positionguardai.com 發布官方發布者v1.0.0更新於 Oct 24, 2026

Ask who in your groups is at an area like home or school. Members opt in. Never coordinates.

已驗證Streamable HTTP可網頁執行位置與生活溝通與協作

概覽

AI 產生的概覽

PositionGuard 讓助理查詢你所在群組中已選擇分享的成員是否在某個區域(例如家或學校),只回傳姓名與人數,不回傳座標。

可以做什麼
  • 列出群組及其中的區域,僅含名稱
  • 查詢某位成員目前所在區域或最後確認所在區域,可限定單一群組或跨全部群組
  • 列出某區域內的成員,區分已確認與最後已知
  • 統計某區域的成員人數,並說明過期與未揭露成員的情況
  • 在成員未分享資訊時回傳明確的 unknown 狀態及原因
適用情境
適合讓聊天助理回答家庭或社團類問題,例如誰在家、某人是否在滑板公園、某場地有多少人,同時尊重每位成員的分享設定。
執行需求
需要 PositionGuard 帳號與類型為 Assistant 的 API 金鑰,透過 POSITIONGUARD_API_KEY 提供,且須以 pg_live_ 開頭。金鑰需具備 presence:read、counts:read 與 groups:read 權限。需要能連線至 PositionGuard API 的網路;伺服器支援 HTTP、SSE 或 stdio 執行。
安裝前請注意
你選擇揭露的位置資訊會傳送給產生回答的模型供應商,雲端模型因此可見。Integration 類型金鑰會繞過成員同意機制,應使用 Assistant 類型。不要授予 areas:read,它會暴露區域座標。此版本的 HTTP 傳輸沒有身分驗證,應僅在 localhost 或私有網路中使用。伺服器為唯讀,不保存座標。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 PositionGuard,將其新增到工作區。
  2. 連接 所需的 憑證。它們會被私密儲存,絕不會在公開頁面上展示。
  3. 為需要使用其工具的對話啟用該服務。

Web executable,透過 Streamable HTTP。 遠端服務在工作區中設定後即可從網頁執行環境執行。

交給代理程式安裝

把這段提示詞貼上到 Claude Code、Codex、Cursor 等代理程式,或 SourceWeft 對話中。代理程式會讀取該伺服器的安裝指南,先向你展示發布者、端點和登入方式,經你同意後再把它新增到你的客戶端。金鑰不會寫進任何命令。

閱讀 https://sourceweft.com/mcp/com.positionguardai%2Fpositionguard/install.md,並按其中說明把這個 MCP 伺服器接入我的客戶端。修改任何設定之前,先告訴我它的發布者、端點和登入方式,等我確認。不要把 API 金鑰寫進命令或我會提交的檔案;問我金鑰應從哪裡取得。

檢視代理程式所依據的安裝指南

其他 MCP 客戶端

把它新增到你客戶端的 mcpServers 設定中。 客戶端會在首次連接時完成 OAuth 登入。

{
  "mcpServers": {
    "positionguard": {
      "type": "http",
      "url": "https://mcp.positionguardai.com/mcp"
    }
  }
}

README

positionguard-mcp

An MCP server that lets an AI assistant ask about PositionGuard presence: is Earl at the skatepark, who is at home, how many people are at the club. It answers only what each member has agreed to share with assistants, and says unknown when they haven't.

The server is a deterministic client of the public REST API. It holds one API key, calls three read-only endpoints, and maps what comes back into a small, explicit vocabulary. There is no model inside it and no privileged access: every guarantee is enforced at api.positionguardai.com, where the key is checked and the consent gate runs. This server cannot widen what the API returns. It can only ask.

What it asks for, and what it doesn't

This server gives the assistant less than it could, on purpose, and you can read exactly what.

  • Three scopes, not four. Grant presence:read, counts:read and groups:read. Do not grant areas:read: it unlocks /groups/{id}/areas, which returns area center coordinates, and this server never calls it. Area names and IDs come from /area-counts, which is coordinate-free by design. The client refuses to fetch /areas even if a future edit asked it to, and a test checks the refusal.
  • An Assistant key, not an Integration key. Create the key in the developer portal with type Assistant. Members are visible to an Assistant key only if they have turned on Let AI agents see whether I'm at an area; otherwise the key gets unknown. An Integration key works mechanically, and bypasses that consent gate. That is the key holder's choice to make; the reference configuration does not demonstrate it.
  • Never log a body, a key, or a name. Log lines carry paths, HTTP statuses, item counts, durations, tool names and the status a tool returned. The key appears once at startup, as its first 16 characters, the same prefix the portal shows.
  • unknown is not "away". A member who has not opted in, who is in global Ghost, who paused sharing, or whose phone has not reported in a while all look identical on the wire, and all map to unknown; the first three are captured in test/fixtures/ and asserted identical. The same holds per area: a member who is at an area but has hidden that one area from assistants is simply not listed there, and that absence is unknown too, never "not there". A member who joined a public group in Ghost mode is not on that roster at all, and one who joined anonymously is listed as "Anonymous"; the API does that before this server sees the roster. The tool descriptions tell the model, in so many words, that unknown means it doesn't know. A stale last-known position is never returned as a current location: it is unknown, or last_known_at_area with its age (below); counts include stale members and say so.
  • No caching. The API caches for 15 seconds. A second cache would be a second place for a withdrawn consent to lag.
  • Read-only. No write tools, no coordinates, no messages, no history. Every tool is annotated readOnlyHint.

Tools

Every answer is JSON with a status field. ok means the question was answered. unknown means it was not, and reason says why in one word. API failures are MCP tool errors carrying the HTTP status in one sentence, and are never retried.

ToolScopeAnswers
list_groupsgroups:read{status, groups: [{group_id, name, group_type}]}
list_areascounts:read{status, areas: [{area_id, name, group_id, group_name}]} — names only, no counts, no geometry
where_is_memberpresence: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_areapresence:read{status, area, confirmed: [nickname…], last_known: [{nickname, last_confirmed_seconds_ago}…], members, last_known_note?, undisclosed_note?, count_note?} — disclosed members only
count_members_at_areacounts:read{status, member_count, stale_count, undisclosed_count, note}

where_is_member looks a nickname up case-insensitively, in one group or across all of them. Its reason is not_disclosed, stale, or no_such_member (which is an answer, not an error, so the model doesn't invent one). who_is_at_area adds count_note ("N counted at this area, M listed") whenever the server counts more members at the area than the answer lists, where N is member_count + undisclosed_count: a public group serves no row for a Ghost-joined member and, above its member limit, no row but the caller's own, and a member can be present but not disclosed to assistants at that area, so the list is never presented as complete when the count says otherwise. When members are undisclosed the note says how many, and "do not report the named list as complete". member_count is a floor when undisclosed_count or stale_count is non-zero, and the note says to report it as "at least N". When the API provides no count at all (archived areas), the answer is unknown with reason count_unavailable, never zero.

Areas inside areas. A group can have a home inside a city. The API lists every area of the group a member is inside, innermost first (inside_areas), and where_is_member answers with the innermost as area and the whole list beside it, so "is Fred in Los Angeles" can be answered from the list. who_is_at_area matches the area asked about against that list, so someone at home is also named for the city. Members choose per area what assistants may see: one who shares Home and not Los Angeles comes back with a list of Home alone, and nothing marks the omission. An area missing from inside_areas is therefore not evidence the member is outside it — the tool description says so — and the only trace of the withholding is the city's undisclosed_count, which is why count_note reads it. since is given only when the area named is the one the API timed the entry for. Against a server that doesn't send inside_areas, the single current_area answers as before.

Freshness and last-known answers. A PositionGuard server can hold a member at the area they were last confirmed in while their phone is silent, and marks those rows position_fresh: false with the position's age. where_is_member answers them as last_known_at_area, with last_confirmed_seconds_ago and a note ("Last confirmed at Lake House 10 h ago; no newer position"), never as at_area. who_is_at_area lists them under last_known, never under confirmed, and count_members_at_area's note says how many of the count are not recently confirmed. The tool descriptions tell the model to relay a last-known answer with its age, never as current presence. since is always when the member entered the area, not when they were last confirmed. Against a server that doesn't send position_fresh, answers are what they were in 0.1.0.

How the wire maps to status is written out in src/core/mapping.ts and tested against captured responses in test/fixtures/ before any model sees the server.

Configuration

EnvRequiredDefault
POSITIONGUARD_API_KEYyes— must start with pg_live_; the server refuses to start otherwise
POSITIONGUARD_API_BASEnohttps://api.positionguardai.com/api/v1
TRANSPORTnohttp (streamable HTTP + SSE) or stdio
PORTno8788 (HTTP transports)
HOSTno0.0.0.0 (HTTP transports); use 127.0.0.1 outside a container

On startup the server calls GET /groups once. A 401 or 403 exits with a message naming the fix. An empty group list starts with a warning. Every tool call re-checks the key, so a briefly unreachable API at startup is logged and not fatal.

Transports

One code path, three ways in. There is no authentication on the HTTP transports in this version: run the server on a private docker network or on localhost, and do not expose it.

PathTransportFor
/mcpStreamable HTTP, statelessOpen WebUI
/sse (+ /messages)SSEHome Assistant's mcp integration, which is SSE-only
stdio (TRANSPORT=stdio)stdioClaude Desktop and other launchers
/healthz—docker healthcheck; returns {"ok":true}

Setups

Each setup is marked tested or untested. Only tested setups describe behaviour that has actually been observed with this server.

1. Open WebUI + Anthropic + this server, in containers — tested

docker-compose.example.yml runs Open WebUI and this server on one private network with no port published for the server. Put WEBUI_SECRET_KEY and POSITIONGUARD_API_KEY in a .env file (gitignored), then:

cp docker-compose.example.yml docker-compose.ymldocker compose up -d

In Open WebUI: Admin → Settings → Connections, add an OpenAI-compatible connection with base URL https://api.anthropic.com/v1 and your Anthropic key. Admin → Settings → External Tools, add an MCP server of type Streamable HTTP at http://positionguard-mcp:8788/mcp with auth None (Bearer with an empty key sends an empty header, which servers reject). Open WebUI has supported Streamable HTTP MCP servers natively since 0.6.31.

Disclosure for this and any other cloud-model setup: presence you've chosen to disclose is sent to the model provider to produce answers.

Verified against the production API with an Assistant key. A member who has turned the setting on returns their area; the same member with it off returns unknown, not "away". A count with an undisclosed or stale member is reported as a floor.

2. Claude Desktop, stdio — tested

Build once (npm install && npm run build), then add to Claude Desktop's claude_desktop_config.json:

json
{  "mcpServers": {    "positionguard": {      "command": "node",      "args": ["/absolute/path/to/positionguard-mcp/dist/index.js"],      "env": {        "POSITIONGUARD_API_KEY": "pg_live_…",        "TRANSPORT": "stdio"      }    }  }}

When the package is published, "command": "npx", "args": ["-y", "positionguard-mcp"] replaces the path. Logs go to stderr; stdout is the protocol.

3. Home Assistant Assist + mcp integration + this server — untested

Run the server with the HTTP transport where Home Assistant can reach it. Add the Model Context Protocol integration and give it the SSE URL, http://<host>:8788/sse. The tools appear under the conversation agent; enable them for the agent (Anthropic, OpenAI, Ollama, …) you use with Assist. Home Assistant's client is SSE-only and offers OAuth or nothing for authentication, so this server's no-auth SSE endpoint is used as-is, on a network you control.

4. Home Assistant Assist + Ollama + this server, fully local — untested

As 3, with Ollama as the conversation agent. No presence leaves your network.

5. Open WebUI + Ollama, fully local — untested

As 1, with Ollama as the model connection instead of Anthropic. No presence leaves your network.

Rate limits and errors

The API rate-limits per key. A 429 is surfaced to the model as a tool error that says how long to wait (from Retry-After), and the server does not retry. The same holds for every other non-2xx status: one request, one answer or one error, nothing in between.

Development

npm installnpm test          # builds, then unit + end-to-end tests over all three transportsnpm run dev       # runs from source (tsx)

src/core/ holds the tools, the wire-to-status mapping and the API client. It imports nothing Node-specific — fetch and AbortSignal only — and a test enforces that, so the same core can run in a Cloudflare Worker with the SDK's web-standard transport when a hosted version is wanted. src/transports/ is the Node side: streamable HTTP, SSE and stdio.

test/fixtures/ holds one response per wire state (test/fixtures/README.md lists them and their provenance). npm run capture -- <group_id> prints redacted responses from a live API for refreshing them; use a synthetic test group.

Why isn't the app open source too?

The components that mediate access are auditable: this server, the Home Assistant integration, the Hubitat app. You can read exactly what each one requests and confirm it asks for less than it could. The hosted service is not open, because the engine inside it — years of drift handling, debounce, staleness distinction, clustering, the visibility layer — is the product, and it is also what makes honest refusal affordable. A system that guesses cannot say "I don't know"; it would be saying it constantly. Wrapping a REST API in MCP is a weekend. Presence that does not flap is not.

License

MIT. See LICENSE.

來源:README.md,提交 68075e3

工具

0
該伺服器需要登入,因此 SourceWeft 無法在沒有帳號的情況下讀取其工具,也未收錄。已連接帳號所能看到的內容可能因帳號及其權限而異。

版本歷史

1
  1. v1.0.0最新Oct 15, 2026

評分

已滿足適用檢查項的 85%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通必需25 分
  • 已宣告認證方式必需10 分
  • 已列出工具15 分

    請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。

  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分