跳到主要内容
版本:开发版

AI 访问(MCP)

你可以让 AI 助手——Claude Desktop、Claude Code、Cursor、VS Code——通过 **Model Context Protocol(MCP)**服务器,代表用户访问 DeviceChain 租户。LLM 客户端连接、发现工具并调用它们,回答设备群问题:“3 号楼有哪些设备在过去一小时没有上报?”、“汇总冷库资产今天的告警”、“恒温器 T-114 的最新温度是多少?”

服务器基于一个原则:AI 代理绝不能做超出授权者权限的事。 它不是权限过宽的网关,而是现有 GraphQL API 上轻量、经过选择的只读层,携带已登录用户自己的租户作用域令牌。

状态

**目前可用(只读):**可选 mcp 服务,提供十一种经过选择的读取工具,通过 user-management 上完整的 OAuth 2.1 授权服务器保护。**计划中:**需要提升作用域且必须人工确认的写入工具(发送命令、确认/解除告警),以及动态客户端注册(RFC 7591)。本仓库是判断当前哪些功能能够构建的权威依据。

授权服务器支持带 PKCE 的授权码流程、RFC 8414 元数据、刷新令牌轮换和 RFC 8707 受众绑定。在提供动态客户端注册之前,由管理员注册客户端。

助手可以做什么​

服务器提供十一种读取工具。每种都是对控制台所用同一 GraphQL API 的查询,使用调用方令牌运行。因此,工具只返回该用户在该租户中有权看到的内容,不会更多。

设备

  • list_devices:列出设备,支持筛选。
  • get_device:获取单台设备详情。
  • get_device_capabilities:获取设备能够测量的内容,以及它接受的已发布命令和各命令的参数模式。

list_devices 和 get_device 目前不会说明设备是否已被禁用,list_devices 也无法按此筛选。已禁用的设备与正常设备的显示完全相同,因此助手无法通过这些工具区分被隔离的设备和正常工作的设备。请在控制台中查看设备状态,已禁用的设备带有 Disabled(已禁用)徽章;参见禁用和启用设备。

实时状态与遥测

  • get_device_state:设备当前最新已知状态,包括该状态是传输协议明确上报还是根据静默推断。这一区别影响“不活跃”的含义。明确上报表示已知设备断开;推断只表示近期没有收到数据,健康但上报间隔较长的设备也可能如此。
  • get_latest_measurements:各测量的最新值。
  • query_measurements:时间范围内的原始时序读数。
  • aggregate_measurements:时间范围内按桶聚合的结果,如最小值、最大值和平均值。startTime 和 intervalSeconds 为必填,endTime 默认为当前时间,单次请求最多 10,000 个桶、50,000 行(桶数乘以返回的测量名称数)。

位置

  • query_locations:设备在可选时间窗口内上报的位置,支持分页并有数量限制。结果按最新优先排列,因此第一条是最新已知位置;不指定时间窗口并只请求一条,就能回答“现在在哪里”。每个位置包含纬度和经度。如果接收器上报了,也包含海拔、精度、速度和航向。缺失字段表示未上报,即未知,而不是零。读取位置需要其他工具不需要的两项条件:客户端还必须获得独立 location 作用域,想同时使用两者时请求 read-only location;用户还必须拥有位置权限,该权限有意不包含在只读查看者基础权限中。因此,其他读取工具都可用的调用方,也可能被此工具拒绝。

告警

  • list_alarms:列出告警,支持按状态和实体筛选。
  • get_alarm:获取单条告警详情。

命令

  • list_commands:列出向设备发出的命令及其状态。

没有通用的“运行这个 GraphQL 查询”工具。敏感读取——凭据、审计记录、通知收件人、配置密钥——有意不包含在工具集中。

安全模型​

MCP 正逐渐成为赋予 AI 助手实际能力的标准方式。风险在于,草率实现可能给代理一把权限强大、范围宽泛的密钥。DeviceChain 服务器的设计防止这种情况发生。

  • 携带用户令牌,绝不用服务令牌。 MCP 服务器不持有任何特权平台凭据。每次工具调用都将调用方经过验证的租户作用域 JWT 转发给底层 GraphQL 服务,使代理只能访问用户能够访问的内容。为 AI 提供独立服务身份会使它能够按任何人的请求跨租户操作,而这是该设计明确拒绝的情况。
  • 授权时固定租户,不通过参数传入。 授权确定令牌可以在哪个租户操作,令牌携带该租户。没有工具接受代理可以修改的“tenant”参数。
  • 令牌绑定受众。 为 MCP 服务器签发的访问令牌将该服务器指定为预期受众(RFC 8707),在其他地方会被拒绝。为一个资源签发的令牌不能拿去访问另一个资源。
  • 只读且工具经过选择。 每种工具都是查询,没有写入路径、通用查询出口,也不暴露敏感对象。
  • 每次调用都认证并重新检查。 每个请求都通过 user-management 公钥验证 bearer 令牌,并强制只读作用域。底层 GraphQL 服务随后独立重新执行与控制台相同的租户和角色检查。

连接助手后,它可以读取租户中的设备、状态、测量和告警。它不能访问其他租户、改变任何内容,或运行任意查询。

记录的内容​

每次工具调用都会在 mcp 服务的日志中写入一行,无论调用结果如何。读取操作不会进入审计日志(审计日志记录的是变更和登录),因此这些日志行是助手读取了什么的唯一记录。一行日志如下所示:

{"instance":"prod","area":"mcp","audit":"mcp_tool_call","schema":1,"tenant":"acme","actor":"ana@example.com","client_id":"claude-desktop","sudo":false,"tool":"query_measurements","args_sha256":"5f1c…","outcome":"ok","duration_ms":42,"time":"2026-10-16T09:12:03Z","message":"MCP tool call"}
字段含义
audit始终为 mcp_tool_call。请按此字段筛选这些记录:它们不带 level。
schema此格式的版本,1。字段的变化会在发行说明中以新的编号公布。
tenant、actor访问令牌所指明的租户和用户。actor 是用户的电子邮件地址。
client_id令牌所签发给的已注册 OAuth 客户端,可据此判断是哪个应用发起了调用。
sudo用户为超级用户时为 true。
tool被调用的工具。调用服务器上不存在的工具会记录为 (unregistered),客户端发送的名称不会被复制。
args_sha256调用参数的 SHA-256,键已排序且去除空白,因此相同的请求具有相同的值。参数本身从不写入。
outcomeok、error(工具拒绝了调用或执行失败,包括其不接受的参数,以及因用户缺少权限而被拒绝)或 rejected(调用了服务器上不存在的工具)。格式错误到连工具名都无法解析的请求,会在到达任何工具之前被协议层拒绝,且不会写入任何记录行。
duration_ms调用耗时。

依赖这些日志行时,需要注意以下几点:

  • 它们在所有日志级别下都会写入。 将 infrastructure.logging.level 设为 warn 或 error 不会阻止它们。
  • 它们位于运维人员的日志中,而不在控制台的审计页面上。 租户管理员看不到它们。请通过收集该服务的日志输出来保存它们;保存多久取决于你的日志管道。
  • 它们包含个人信息。 actor 是电子邮件地址。删除租户不会影响已经收集的日志行,因此如果你的保留规则有此要求,请在日志存储中删除已删除租户的日志行。
  • 摘要用于匹配调用,而不是隐藏调用。 参数是设备令牌、过滤条件和时间范围。知道某租户设备的人可以为猜测的请求重新计算摘要。任何工具参数都不携带凭据。
  • 每次调用一行。 一行约几百字节,因此日志量随调用频率变化。列出工具和打开会话不会写入任何内容。

客户端如何连接​

MCP 服务器是 OAuth 2.1 资源服务器,user-management 是其授权服务器。客户端连接使用标准 OAuth 流程,而不是自定义密钥交换:

  1. 客户端从受保护资源元数据(RFC 9728)读取服务器要求,再从授权服务器的元数据(RFC 8414)发现授权信息。每个文档都位于 well-known 路径:将 well-known 片段插在主机与标识符路径之间。实例位于 iot.example.com 时,路径分别是 /.well-known/oauth-protected-resource/api/mcp 和 /.well-known/oauth-authorization-server/api/user-management。
  2. 客户端引导用户完成带 PKCE 的授权码流程(/oauth/authorize)。用户登录、选择要授权的租户并同意。全部由服务器渲染,不使用共享密钥。该页面使用密码登录用户;若其组织已配置单点登录,则可使用 Sign in with SSO:用户输入工作邮箱,在其身份提供商处登录,然后回到服务器保留的同一个授权请求。即使身份提供商静默完成登录,同意页面也始终会显示。要求单点登录的租户会拒绝密码表单,因此仅使用 SSO 的组织同样通过这种方式连接 MCP 客户端,授权会随该次登录结束而结束(达到会话上限,或管理员将此人在所有位置退出登录)。
  3. 客户端在 /oauth/token 将授权码换为租户作用域访问令牌,并按需刷新。刷新令牌只可使用一次并会轮换。授权依附于在授权页面上完成的登录,它与任何控制台登录相互独立,因此在控制台中退出登录不会终止授权。无论是否刷新,授权最长持续刷新令牌的有效期(默认 7 天),从该次登录起算;重置用户密码、禁用或删除用户会提前终止授权。两种情况下,下一次刷新都会被拒绝,客户端必须重新授权。
  4. 客户端使用该令牌调用 MCP 工具,每次调用都按用户自身权限运行。

管理员通过管理 API 注册客户端,客户端不自行注册。因此,运维人员控制哪些应用可以请求访问,以及允许哪些重定向 URI。

客户端应该指向哪里​

你只配置一个 URL:实例公共主机加上 /api/mcp。

https://<your-instance-host>/api/mcp

这一个字符串具有三种用途,因此只需要它:

  • 客户端 POST MCP 请求的端点;
  • 请求令牌时作为 resource 参数发送、并作为令牌受众的资源标识符;
  • 推导其他信息的发现起点。

无需手动输入其他内容。端点返回 401 时,客户端从响应 WWW-Authenticate 标头读取元数据位置并访问。文档告诉它授权服务器在哪里,然后客户端向授权服务器请求自己的元数据。发现过程就是这三个请求,可以先手动验证:

# 1. The endpoint answers 401 and names its metadata document.
curl -i -X POST https://<your-instance-host>/api/mcp

# 2. That document names the authorization server.
curl https://<your-instance-host>/.well-known/oauth-protected-resource/api/mcp

# 3. The authorization server describes where to log in and get a token.
curl https://<your-instance-host>/.well-known/oauth-authorization-server/api/user-management

well-known 片段位于主机和其余路径之间,而不是之后。起初看起来可能不直观,但标准就是这样规定带路径标识符的位置,因此客户端也会这样构造。对于第二个文档,更直观的 https://<host>/api/mcp/.well-known/oauth-protected-resource 也提供同样内容,以兼容采用这种路径构造方式的客户端。

三个请求都无需认证:发现过程有意公开,不返回密钥。请求 3 只有在授权服务器启用后才响应;参见下文,了解为什么这需要独立步骤。

只运行一个副本​

MCP 服务器将各客户端协议会话保存在创建会话的 Pod 内存中。会话不在 Pod 间共享,也没有会话亲和性。运行第二个副本时,每个客户端约一半的请求会到达从未见过该会话的 Pod,并被拒绝。错误间歇发生,消息不会提到扩容,因此看起来像客户端缺陷。

必须只运行一个副本

安装 mcp 区域时,多个副本会直接被拒绝,而不是留待运行时出现间歇失败。

限制与边界​

有些限制是设计决策,有些是当前实现的边界。下文分组说明。

有意设计,且未计划改变:

  • 没有写入。 计划通过 MCP 发送命令或确认告警,但必须提升作用域并且显式人工确认。助手绝不会静默驱动设备。
  • 没有跨租户访问。 令牌限定于用户授权时选择的一个租户。租户绝不是工具参数,因此代理无法通过更改参数访问其他租户。
  • 没有任意查询。 只能访问选定工具集,没有 run_graphql。
  • 任何执行路径都不使用服务凭据。 这比“不使用服务令牌”更强:MCP 服务器的任何代码路径都不获取自身凭据,代理没有可借用的权限。所有下游读取都使用调用方令牌,没有读取权限的代理会收到与用户相同的拒绝。(其 Pod 与其他服务一样挂载实例配置,这是部署配套,不是服务器使用的内容。)
  • 不返回命令载荷。 list_commands 提供名称、状态和时间信息。发送给设备的内容不会进入代理上下文。

最先会遇到的实现限制:

  • 结果默认每页 25 条,最多 100 条;多设备查询每次最多 50 个令牌。代理检查大型设备群时需要分页。
  • 服务器从单个下游响应最多读取 8 MiB。这限制从平台 API 获取的内容,不是向代理公布的限制。
  • 会话空闲 30 分钟后失效。
  • 单个 JSON-RPC 批处理最多包含 16 条消息;每个调用方(令牌对应的用户)每秒最多发送 20 条消息,突发上限为 40;包含 N 条消息的批处理按 N 条计。超过上限的批处理会被整体拒绝并返回 JSON-RPC 错误(其中任何内容都不会执行);超过速率的调用方会收到带 Retry-After 头的 HTTP 429。如需调整,请在 functionalAreas.mcp.config 下设置 maxBatchEntries、callsPerSecond 和 callsBurst;callsBurst 不得小于 maxBatchEntries,无效取值会使服务在启动时终止。

仅启用服务还不足以使用。 有两个独立开关。只开启第一个,通常会得到能够响应却无法实际访问的服务器:

  1. 默认部署不包含 mcp 功能区域,需要运维人员显式启用。
  2. user-management 上的授权服务器在配置 issuer URL 前保持关闭。在此之前,mcp 可以启动并提供元数据,但没有客户端能获得令牌。这是有意的独立开关:设置 issuer 会改变实例签发的所有令牌上的一个声明,而不只是 MCP 使用的令牌。