
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.
概览
PositionGuard 让助手查询你所在群组中已选择共享的成员是否在某个区域(如家或学校),只返回姓名和人数,不返回坐标。
- 可以做什么
- 列出群组及其中的区域,仅含名称
- 查询某位成员当前所在区域或最后确认所在区域,可限定单个群组或跨全部群组
- 列出某区域内的成员,区分已确认与最后已知
- 统计某区域的成员人数,并说明过期与未披露成员的情况
- 在成员未共享信息时返回明确的 unknown 状态及原因
- 适用场景
- 适合让聊天助手回答家庭或社团类问题,例如谁在家、某人是否在滑板公园、某场地有多少人,同时尊重每位成员的共享设置。
- 运行要求
- 需要 PositionGuard 账户和类型为 Assistant 的 API 密钥,通过 POSITIONGUARD_API_KEY 提供,且以 pg_live_ 开头。密钥需具备 presence:read、counts:read 和 groups:read 权限。需要能访问 PositionGuard API 的网络;服务器支持 HTTP、SSE 或 stdio 运行。
安装
在 SourceWeft 中
- 打开 控制台中的 PositionGuard,将其添加到工作区。
- 连接 所需的 凭据。它们会被私密存储,绝不会在公开页面上展示。
- 为需要使用其工具的对话启用该服务。
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:readandgroups:read. Do not grantareas: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/areaseven 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
statusa tool returned. The key appears once at startup, as its first 16 characters, the same prefix the portal shows. unknownis 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 tounknown; the first three are captured intest/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 isunknowntoo, 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, thatunknownmeans it doesn't know. A stale last-known position is never returned as a current location: it isunknown, orlast_known_at_areawith 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.
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
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.
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:
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:
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
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版本历史
1- v1.0.0最新Oct 15, 2026
评分
已满足适用检查项的 85%
该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。
- 近 30 天内可连通必需25 分
- 已声明认证方式必需10 分
- 已列出工具15 分
请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。
- 描述必需15 分
- 图标10 分
- 文档15 分
- 许可证10 分