关于 PositionGuard
PositionGuard MCP 服务让 AI 助手查询群组成员是否在指定区域,仅返回成员同意分享的信息;不读取坐标,并将不可用或未披露的状态标为未知。
主要功能
- 01查询成员所在区域及最近确认位置
- 02列出群组和区域名称
- 03查询区域成员及人数
- 04尊重成员与单个区域的分享设置
- 05只读操作,不获取坐标或历史
适用场景
- 01询问某位成员是否在家或学校
- 02查看某个区域有哪些已披露成员
- 03统计区域内成员数并识别过期位置
MCP 相关
提供 `list_groups`、`list_areas`、`where_is_member`、`who_is_at_area` 和 `count_members_at_area` 等只读 MCP 工具。支持 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
一个 MCP 服务,让 AI 助手可以查询 PositionGuard 中的 位置状态:Earl 在滑板公园吗、谁在家、俱乐部里有多少人。它只会回答每位成员同意向 助手分享的信息;如果成员没有同意,就会回答 unknown。
该服务是公共 REST API 的确定性客户端。它持有一个 API 密钥,调用三个只读端点,并将返回
的数据映射为一组精简、明确的词汇。服务内部没有模型,也没有特权访问:所有保证都由
api.positionguardai.com 执行,该服务会检查密钥并运行同意门控。这个服务无法扩大 API
返回的范围,只能发起查询。
它会查询什么,以及不会查询什么
这个服务有意向助手提供少于其所能提供的信息,而且你可以确切了解提供了什么。
- 三个作用域,而非四个。 授予
presence:read、counts:read和groups:read。不要授予areas:read:它会开放/groups/{id}/areas,该端点会返回区域中心坐标,而这个服务从不调用它。区域名称和 ID 来自/area-counts,该端点在设计上不包含坐标。即使未来的修改要求获取/areas,客户端 也会拒绝,并且有测试验证这一拒绝行为。 - 使用 Assistant 密钥,而非 Integration 密钥。 在开发者门户中创建类型为 Assistant 的密钥。只有开启了 允许 AI agents 查看我是否在某个区域 的成员,才会对 Assistant 密钥可见;否则密钥会得到 unknown。Integration 密钥在技术上可用,但会绕过 该同意门控。是否这样做由密钥持有者决定;参考配置不会演示这种用法。
- 绝不记录响应正文、密钥或姓名。 日志行包含路径、HTTP 状态码、项目数量、耗时、工具
名称,以及工具返回的
status。密钥只会在启动时出现一次,并且仅显示前 16 个字符,与 门户中显示的前缀相同。 unknown不等于“已离开”。 未选择加入、处于全局 Ghost 模式、暂停分享,或手机有一段 时间未上报位置的成员,在网络响应中看起来都一样,都会映射为unknown;前三种情况都在test/fixtures/中提供了测试数据,并断言结果相同。每个区域也是如此:如果成员在某个 区域,但对助手隐藏了该区域,就不会在该区域的列表中出现;这种缺失同样表示unknown, 绝不表示“人不在这里”。以 Ghost 模式加入公开群组的成员根本不会出现在该群组名单中,而 匿名加入的成员则会显示为“Anonymous”;API 会在这个服务获取群组名单之前处理这些情况。 工具描述会明确告诉模型,unknown表示它不知道。过时的最近已知位置绝不会作为当前位置 返回:它会显示为unknown,或显示为last_known_at_area并附上其时间(见下文);人数 统计会包含位置过时的成员,并注明这一点。- 不缓存。 API 会缓存 15 秒。再增加一层缓存,就多了一个可能延迟反映撤回同意的环节。
- 只读。 没有写入工具、坐标、消息或历史记录。每个工具都标注为
readOnlyHint。
工具
每个回答都是带有 status 字段的 JSON。ok 表示问题已得到回答。unknown 表示未能回答,
reason 会用一个词说明原因。API 故障会作为 MCP 工具错误返回,并用一句话包含 HTTP 状态码,
且绝不会重试。
| 工具 | 范围 | 返回内容 |
|---|---|---|
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 会以不区分大小写的方式查找昵称,可在单个群组中查找,也可跨所有群组查找。其 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 会读取它的原因。只有当 API 为指定区域记录了进入时间时,才会提供 since。对于不发送 inside_areas 的服务端,答案仍与之前一样,使用单个 current_area。
数据新鲜度与最后已知位置答案。 PositionGuard 服务端可以在成员手机无响应时,将其保留在最后一次确认所在的区域,并将这些行标记为 position_fresh: false,同时附上位置的时效。where_is_member 会将其回答为 last_known_at_area,并附上 last_confirmed_seconds_ago 和备注(“10 小时前最后确认在 Lake House;之后没有更新位置”),绝不会回答为 at_area。who_is_at_area 会将其列在 last_known 下,而非 confirmed 下;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 短暂不可达只会记录日志,不会导致启动失败。
传输方式
同一条代码路径,三种接入方式。此版本的 HTTP 传输没有身份验证:请在私有 Docker 网络或 localhost 上运行服务,切勿将其暴露在外。
| 路径 | 传输方式 | 适用对象 |
|---|---|---|
/mcp | 可流式传输的 HTTP,无状态 | Open WebUI |
/sse(+ /messages) | SSE | Home Assistant 的 mcp 集成,该集成仅支持 SSE |
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 文件(已加入 gitignore),然后运行:
cp docker-compose.example.yml docker-compose.yml
docker compose up -d
在 Open WebUI 中:前往管理 → 设置 → 连接,添加一个与 OpenAI 兼容的连接,基础 URL 为 https://api.anthropic.com/v1,并填写你的 Anthropic 密钥。前往管理 → 设置 → 外部工具,添加一个类型为 Streamable HTTP 的 MCP 服务,地址为 http://positionguard-mcp:8788/mcp,身份验证选择无(Bearer 搭配空密钥会发送空请求头,服务端会拒绝)。从 0.6.31 起,Open WebUI 原生支持 Streamable 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 集成 + 此服务 — 未测试
使用 HTTP 传输运行服务,并确保 Home Assistant 能访问该服务。添加 Model Context Protocol 集成,并提供 SSE URL:http://<host>:8788/sse。这些工具会显示在对话 Agent 下;请为你与 Assist 搭配使用的 Agent(Anthropic、OpenAI、Ollama 等)启用这些工具。Home Assistant 客户端仅支持 SSE,身份验证只能选择 OAuth 或不启用,因此此服务无身份验证的 SSE 端点会直接使用;请确保网络由你控制。
4. Home Assistant Assist + Ollama + 此服务,完全本地运行 — 未测试
与方案 3 相同,但使用 Ollama 作为对话 Agent。在场信息不会离开你的网络。
5. Open WebUI + Ollama,完全本地运行 — 未测试
与方案 1 相同,但使用 Ollama 作为模型连接,而非 Anthropic。在场信息不会离开你的网络。
速率限制和错误
API 会按密钥进行速率限制。429 会作为工具错误返回给模型,并说明需要等待多久(取自 Retry-After);服务不会重试。其他所有非 2xx 状态也一样:一次请求,一条响应或一个错误,不会有其他处理。
开发
npm install
npm test # builds, then unit + end-to-end tests over all three transports
npm run dev # runs from source (tsx)
src/core/ 存放工具、从线路状态到状态的映射,以及 API 客户端。
它不导入任何 Node 专用内容——仅使用 fetch 和 AbortSignal——并且有测试确保这一点,因此当需要托管版本时,同一核心也能通过 SDK 的 Web 标准传输在 Cloudflare Worker 中运行。
src/transports/ 是 Node 端:可流式传输的 HTTP、SSE 和 stdio。
test/fixtures/ 为每种线路状态存放一个响应(test/fixtures/README.md
列出了这些响应及其来源)。运行 npm run capture -- <group_id> 可从实时 API 输出脱敏后的响应,以便更新测试样例;请使用合成测试组。
为什么应用也不开源?
负责中介访问的组件都可供审计:此服务、Home Assistant 集成、Hubitat 应用。你可以准确查看每个组件会请求什么,并确认它请求的内容少于其可能请求的内容。托管服务不开源,因为其中的引擎——历经数年积累的漂移处理、防抖、过期状态区分、聚类和可见性层——才是产品本身,也正是它让坦诚地拒绝回答变得经济可行。会猜测的系统无法说“我不知道”;它只会一直这么说。将 REST API 封装成 MCP 只需一个周末;让状态保持稳定则不然。
许可证
MIT。请参阅 LICENSE。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。