跳转到主要内容

订阅 MCP 相关活动

通过 MCP 连接到 Kanera 的 AI 代理可以在工作发生变化时请求告知,而不是进行轮询。 Kanera实现草稿的webhook投递 MCP 活动 extension: 代理的主机注册 HTTPS 回调,Kanera 验证它,从那时起,每个匹配的更改都会作为签名 JSON 实例发布到它。

这就是ChatGPT这样的客户端中"观察这个看板并在评论发生时通知我"的机制。 主机拥有回调;你不需要运行服务器或在Kanera中粘贴URL。

这是给谁的 如果您使用 ChatGPT,请阅读 观察 ChatGPT 中的变化 此页面的其余部分是线程级合同,适用于构建自己的 MCP 主机或自定义集成的人,以及任何想要准确了解代理可以和不能被告知的内容的人。 : :

代理可以订阅什么​

事件发射时额外字段 data
card.created在监控范围内创建一张卡片。title, listId, url
card.updated卡会更改:标题、描述、完成、存档状态和其他卡字段。title, listId, url
card.moved卡片在列表或板内或之间移动。listId (目的地), fromListId (来源), prevPosition, url
card.blocked活动卡获得其第一个开放阻止程序,包括重新开放或恢复。title, listId, url;没有拦截器详细信息
card.unblocked通过完成、存档、取消链接或删除拦截器的板或列表,活动卡将丢失其最后打开的拦截器。title, listId, url;没有拦截器详细信息
schedule.conflict_detected开放式卡牌获得其第一个调度冲突:开放式阻止程序计划在其开始时或开始后完成。blockerCardId, days (家属需要搬家的工作日)
schedule.conflict_resolved打开的卡会丢失其最后的调度冲突。无额外的时间表字段
schedule.critical_path_slipped关键路径卡的计划末端移动较晚,并延迟了其板的预计完成时间。previousProjectedFinish, projectedFinish, days, slipDays
comment.created评论张贴在卡片上。commentId, text (最多 8,000 个字符), url
priorities.changed你的 接下来 队列可能已更改。无。 下一条队列.
my_day.ready是你们时区的一个平日早上。localDate, timeZone。参见 您的早晨简报.
reminder.due你的一个 卡提醒 到期。reminderId, cardId, remindAt, url。参见 您的卡提醒.

每张卡片和评论发生带有 workspaceId, boardId, cardId,和一个 actor (见 告诉你自己的写作分开)。有效负载是有限的摘要:卡片的描述、附件和完整的评论线程永远不会包括在内。代理读取当前记录 cards.get 或 comments.list 当它需要更多的时候。

对于卡片,评论和安排事件,订阅 论点 缩小交付内容的范围:

论证必要效果
workspaceId是仅此工作区的事件。
boardId没有仅此论坛的事件。 论坛访客必填。
listId没有仅为此工作区列表中的卡片发生事件。 card.moved,进入或离开列表的卡片会匹配,但在列表中重新排序则不会。省略 boardId 观看每个可访问的板上的共享列表。
cardId没有仅此卡片的事件。 需要 boardId.

每次交付时,访问都遵循连接的身份,而不仅仅是在订阅时。工作区成员可以观看整个工作区;跨组织来宾必须指定他们受邀参加的看板;仅限指定物品的客人必须说出他们当前指定的卡片。如果该身份后来失去了对该范围的访问权限,则交付将停止并且订阅将过期。失去对看板范围订阅内的一张卡的访问权限只会跳过该事件。

附属机构的过渡​

card.blocked 以及 card.unblocked 跟随 卡依赖关系,包括人员、集成、自动化和镜像卡的变化。它们不需要自动化规则。添加更多阻止程序或仅清除多个阻止程序中的一个不会重复转换。每个方向仅限每张卡每次交易一次。

该事件描述了从属卡,并省略了阻止器详细信息以保护私有板。 Read cards.get 检查当前状态,然后再执行操作,因为自动化可能已经移动或完成了卡片。

时间表过渡​

三个 schedule.* 与前面的方法相同。 时间轴视图 以及 work.schedule。它们需要在有效的组织/工作区设置中启用时间轴和依赖关系,并且不需要自动化规则。

  • schedule.conflict_detected 是从没有调度冲突到至少有一个冲突的过渡。 cardId 指定受抚养人, blockerCardId 列出最糟糕的冲突前提, days 是从属需要移动到右侧的周一至周五工作日数。 添加另一个冲突不会重复它。
  • schedule.conflict_resolved 意味着一个打开的卡片没有剩下的冲突,例如,在日期变更,阻止器完成,或依赖关系被移除后。 仅清除几个冲突中的一个不会触发它,以后的重复可以再次触发检测。
  • schedule.critical_path_slipped 需要关键卡本身的计划结束时间推迟,以及板的预计完成时间推迟。 由于间隙而吸收的日期变更不符合资格,仅仅添加新工作也不符合资格。 days 测量投影移动的距离; slipDays 预测可以推迟,而预测的时间范围可以延长。 slipDays 仍为零。

初始调度评估建立了一个基线,而不是宣布所有现有的冲突。 work.schedule 当开始观看或从错过的交付恢复时,它会显示当前状态。 在建议更改之前,在事件上再次阅读它。 这些警报不会重新安排卡片。

对于自定义主机,请分别订阅每个事件名称。 此请求监视一个板上的第一个冲突;替换实际的工作区和板 UUID 以及主机的验证回调和签名密码:

{
"jsonrpc": "2.0",
"id": 3,
"method": "events/subscribe",
"params": {
"name": "schedule.conflict_detected",
"arguments": { "workspaceId": "…", "boardId": "…" },
"delivery": {
"mode": "webhook",
"url": "https://receiver.example.com/kanera/events",
"secret": "whsec_…"
},
"ttlMs": 86400000
}
}

关键路径失误的发生看起来像这样(ID 和日期是说明性的):

{
"eventId": "evt_…_schedule.critical_path_slipped",
"name": "schedule.critical_path_slipped",
"timestamp": "2026-11-02T10:00:00.000Z",
"data": {
"workspaceId": "…",
"boardId": "…",
"cardId": "…",
"previousProjectedFinish": "2026-11-06",
"projectedFinish": "2026-11-09",
"days": 1,
"slipDays": 1,
"actor": { "kind": "user", "userId": "…", "self": false }
},
"cursor": null
}

日期为 YYYY-MM-DD;持续时间、宽裕时间和滑动计数不包括周末。卡片和时间表事件可能描述相同的底层编辑,但具有不同的事件 ID。重复数据删除重试方式 eventId 并检查 actor.self 在对您自己的更改做出反应之前。 规划示例.

下一条队列​

priorities.changed 告诉代理,连接用户自己的 Up next 队列可能已发生变化,因此代理可以重新读取队列而不是进行投票。 它在每次重新排序队列或调整队列大小时都会触发:

  • 添加、移动或删除条目,无论是从 Web 应用、API 或其他代理;
  • 队列中的卡片完成、存档、恢复或重新分配,从而将其从实时队列中移除或重新放入。

它不需要参数(通过) {})并且永远不会将范围限定为工作区,因为队列跨越您工作的每个工作区。事件仅包含更改者:

"data": {
"targetUserId": "…",
"actor": { "kind": "user", "userId": "…", "self": false }
}

targetUserId 总是连接的用户。 没有列出队列卡片的任何信息。 Call priorities.list 当前排名;它适用于您的正常卡片可见性。

你只能看着 你的自己的 没有参数可用于其他人的队列,包括可以通过 priorities.list。即使没有内容的通知也会显示观察者看不到的卡片何时以及由谁完成或重新分配。访客或只读 API 密钥也可以监视自己的队列。

您的早晨简报​

my_day.ready Kanera 会在您的个人资料中显示的时区的 08:00 点,即与您的客服人员每天工作时间上午的通知时间同步,每天发送一次通知。 每日摘要电子邮件。它遵循摘要的设置:当您的每日摘要电子邮件关闭时,不会发送 ping。无论是否有任何到期的事情都会发送,因此代理仍然可以报告晴天。

它不需要参数(通过) {})并且只描述你自己的一天。该事件没有任何作用:

"data": {
"targetUserId": "…",
"localDate": "2026-10-12",
"timeZone": "Europe/London",
"actor": { "kind": "system", "userId": null, "self": false }
}

代理商然后呼叫 work.my_day 与此同时 timeZone。一次调用即可返回您的逾期卡、未来 7 天内到期的卡、包含逾期清单项目的卡、闲置 14 天或更长时间的指定卡以及下一个队列的顶部,所有这些都会根据您当前的访问权限进行检查。

简报还回来 schedule.conflicts, schedule.critical, 和 schedule.lateToStart:您分配的有冲突的卡、没有冲突的关键路径卡以及计划开始时间已过但从未进入进度的卡。后期启动检查需要进行时间跟踪和进行中列表,并排除已经逾期的卡。

订阅每次刷新最多持续 24 小时,因此客户端必须不断刷新才能接收第二天早上的 ping。 ChatGPT 在任务处于活动状态时执行此操作。

您的卡提醒​

reminder.due 当您的客户之一 卡提醒 提醒是通过卡片上的警报按钮设置的,或者由具有相应的语音识别功能的代理人设置的。 cards.set_reminder。它不需要任何参数(通过 {})并且仅涵盖您自己的提醒;没有人可以订阅别人的。

Kanera 每隔30秒检查一次是否有到期提醒,并且每次发送一次提醒。 重新安排已经触发的提醒会重新启动它,下一次触发是新的提醒。 提醒会给卡片命名,但不会包含任何内容:

"data": {
"targetUserId": "…",
"reminderId": "…",
"cardId": "…",
"remindAt": "2026-10-16T07:00:00.000Z",
"url": "https://board.kanera.app/o/…/c/MKT-42",
"actor": { "kind": "system", "userId": null, "self": false }
}

代理人打电话 cards.get 与 cardId 交付检查您是否仍然可以看到卡片;卡片上的提醒自此已存档,或您无法再访问,但不会触发通知或事件。

观察 ChatGPT 中的变化​

使用下面的命令安装并连接 Kanera 插件 ChatGPT设置指南,然后在支持事件驱动任务的工作对话中选择它。兼容主机自动发现事件功能。对于自定义 MCP 连接,请在中打开它 插件 并选择 刷新 已发布的插件工具更新遵循 OpenAI 的审查流程。

在附加了 Kanera 应用的对话中,询问您想要发生什么:

Watch the Product board in Kanera. When a new comment appears on any card, summarise it for me and suggest a reply.
Tell me whenever a card moves into the Done list on the Launch board.
Whenever my Up next queue changes, tell me what is now at the top.
Every weekday morning, brief me on what is overdue, what is due this week, and what is at the top of my Up next queue.
Watch the Launch board for scheduling conflicts and critical-path slips. Read the current schedule when one appears, tell me which cards and dates are affected, and propose a repair. Tell me when conflicts clear. Do not change dates.

ChatGPT 在其自己托管的回调上创建订阅,在任务处于活动状态时刷新订阅,并在您停止任务时结束订阅。 Kanera 永远不会将您的工作发送到任何 ChatGPT 尚未证明其控制的地方(请参阅 回调验证).

更多的工作例子,包括回答卡上的问题,分类新的工作,并保护一个单一的卡,在 使用示例.

写任务时要知道两件事:

  • 避免循环。 每个实例都会指定其演员的名称,以及订阅连接是否引起了该实例。 actor.self 再写之前,或者它会自行回答。
  • 写作之前先读一读。 请求任务先读取卡片,并使用工具的等位键来进行可重复的更改。

交付是使用有限重试窗口的最大努力(参见 交付和重试)。如果 ChatGPT 的回调无法到达的时间超过该时间,则错过该事件;任务通过读取当前状态来恢复。

议定书​

事件仅在托管的 HTTP 端点上可用, https://mcp.kanera.app/mcp 或是你自己的部署 /mcp,并且仅适用于当前 MCP 协议上的客户端,这就是添加事件订阅的原因。 kanera mcp over stdio 不提供它们:本地 stdio 主机没有托管回调来传递。

server/discover 宣传两者下的能力 capabilities.events (ChatGPT 今天读到的内容)和 capabilities.extensions["io.modelcontextprotocol/events"] (扩展提案放置的位置)。接下来是三个请求方法,其身份验证与工具调用完全相同,并使用 OAuth 访问令牌或 API 密钥作为承载。以下示例仅显示方法参数;客户端库添加协议的每个请求标头和元数据。

events/list​

返回上面的目录,其中包含每个事件的参数和有效载荷的 JSON 架构。 目录是一个单一页面; cursor 必须是空白的。

{
"jsonrpc": "2.0",
"id": 1,
"method": "events/list",
"params": {}
}

events/subscribe​

{
"jsonrpc": "2.0",
"id": 2,
"method": "events/subscribe",
"params": {
"name": "comment.created",
"arguments": { "workspaceId": "…", "boardId": "…" },
"delivery": {
"mode": "webhook",
"url": "https://receiver.example.com/kanera/events",
"secret": "whsec_…"
},
"ttlMs": 86400000
}
}
外地规则
delivery.mode只有 webhook 任何其他模式都会返回 -32014 Unsupported 与 data.feature = "deliveryMode".
delivery.urlHTTPS,URL 中无凭据,无片段,必须解析为公共地址,拒绝回环、私有、链接本地、云元数据和 IPv4 映射或过渡 IPv6 地址。
delivery.secretwhsec_ 然后是24到64字节的base64,Kanera将其加密存储,并用它签署每次交付。
ttlMs默认值和最大值为 24 小时;如果正值较短,则会被接受,如果较长,则会被拒绝。 null (无有效期)上限为 24 小时。
cursor, maxAgeMs因兼容性而接受。 Kanera 不提供重播: cursor 总是返回为 null,并且非空请求游标被报告为 truncated: true.

答复:

{
"id": "sub_…",
"refreshBefore": "2026-10-07T13:00:00.000Z",
"cursor": null,
"truncated": false,
"deliveryStatus": {
"active": true,
"lastDeliveryAt": "2026-10-06T12:58:10.000Z",
"lastError": null
}
}

订阅 id 对于连接、回调 URL、事件名称和参数(对于 priorities.changed, my_day.ready, 和 reminder.due,连接的用户),所以调用 events/subscribe 是同一条路。 刷新: 延伸了 refreshBefore,接受一个新的 secret (新旧密签均交付五分钟),并返回 deliveryStatus 对于仍然处于活动状态的订阅。 deliveryStatus 首次订阅和到期后续订时省略。 刷新前 refreshBefore;稍后续订的过期订阅从续订时间开始重新开始,不会重播错过的内容。

active 总是 true:Kanera 绝不会因传送失败而暂停订阅。相反,它限制每次发生的重试,并通过以下方式报告端点的运行状况: lastError (其中之一 connection_refused, timeout, tls_error, http_4xx, http_5xx, challenge_failed)和 failedSince,在端点持续失败时出现。

一个连接最多可以容纳 100 个活动订阅。第101次回归 -32013 ResourceExhausted.

events/unsubscribe​

采取相同的 name, arguments, 和 delivery.url,毫无秘密。只有创建订阅的连接才能结束它;另一个凭证的请求是空操作。取消订阅是幂等的。

错误​

Kanera 使用错误代码来草拟文档的事件。 订阅失败结构化 data 所以主机可以在不解析消息的情况下对它们采取行动。

代码姓名何时
-32011NotFoundname 您访问的页面不存在data.kind = "event").
-32012Forbidden身份无法看到请求的作用域,或者凭据不是 API 密钥或 OAuth 连接。
-32013ResourceExhausted订阅限制 (data.max)或速率限制。
-32014Unsupported除以下以外的交付方式 webhook (data.feature, data.value).
-32015CallbackEndpointError核实失败。 data.reason 是其中之一 connection_refused, timeout, tls_error, http_4xx, http_5xx, 或 challenge_failed。被拒绝的目的地报告为 challenge_failed,因此错误响应不能用于映射专用网络。
-32602InvalidParams格式错误的请求、无效的密钥或 URL、工作区、看板或卡片 arguments 404 - 页面不存在 页面不存在. priorities.changed, my_day.ready, 或 reminder.due.

回调验证​

在存储订阅之前,Kanera 会证明回调是您的。它将验证请求发布到 delivery.url,使用您提供的秘密进行签名:

{ "type": "verification", "challenge": "…" }

端点必须回答 2xx 使用一个能够准确反映挑战的 JSON 主体:

{ "challenge": "…" }

其他任何内容都会导致订阅失败 -32015 成功的验证会被缓存五分钟,每次连接和回调URL,所以刷新或更改验证结果会导致重定向失败。 arguments 没有重复挑战。

验证运行时使用与交付相同的严格目标策略:仅公共 HTTPS 地址,解析一次并为连接固定,以便在检查和请求之间的 DNS 更改无法重定向。

交付和重试​

每次发生都是一次 POST 返回到顶部 Content-Type: application/json:

{
"eventId": "evt_…",
"name": "comment.created",
"timestamp": "2026-10-06T12:58:09.412Z",
"data": {
"workspaceId": "…",
"boardId": "…",
"cardId": "…",
"commentId": "…",
"text": "Can we ship this on Thursday?",
"url": "https://board.kanera.app/o/3F2A9C1D7B4E6A08/c/PROJ-123",
"actor": { "kind": "user", "userId": "…", "self": false }
},
"cursor": null
}
头条内容
webhook-id该 eventId。重试后保持稳定;使用它使处理程序幂等。
webhook-timestamp当这次尝试被签名时, Unix 秒钟。 每次重试时刷新。
webhook-signature一个或多个空格分隔 v1,<base64> 签名(秘密轮换期间有两个)。
X-MCP-Subscription-Id订阅 id.

实体字节在每次尝试中都是相同的;只有时间戳和签名会发生变化。

回应 2xx 要承认。 Kanera 下面是例子:

回应行为
2xx送达,尸体被忽略
410, 413,以及其他 4xx此实例将被丢弃。 订阅将继续;未来的实例仍将交付。
408, 425, 429, 5xx、超时、TLS 或连接失败重试同样的 eventId:总共五次尝试,时间大约为 0 秒、30 秒、1.5 分钟、3.5 分钟和 7.5 分钟。之后该事件就会被删除。

超过 4 KiB 的响应体将被丢弃,不影响结果。超过 256 KiB 的事件永远不会被发送。 Kanera 保留交货记录 14 天。

活动订阅的交付会在 Kanera 次重新启动后继续进行。 在每次尝试之前都会重新检查授权,因此断开连接的代理、撤销的密钥、删除的成员或丢失分配的宾客在下一次尝试时停止接收数据,而不是在下一次刷新时停止接收数据。

priorities.changed 由于队列不与单个工作区绑定,因此在队列更改保存的那一刻就会排队,而不是从卡片事件使用的持久变更日志中排队。 在罕见的情况下,如果Kanera在此时重新启动,则可能会丢失一条通知;下一条通知将在该工作区中发送。 priorities.list read 总是当前的。

验证签名​

这个方案是 标准Webhooks 后来,他又回到了家里。 whsec_ 从base64,HMAC-SHA256字符串的前缀 {webhook-id}.{webhook-timestamp}.{body},并在恒定时间内与标头中的每个签名进行比较:

import crypto from "node:crypto";
import express from "express";

const app = express();
const key = Buffer.from(process.env.KANERA_MCP_EVENT_SECRET!.slice("whsec_".length), "base64");

app.post("/kanera/events", express.raw({ type: "application/json" }), (req, res) => {
const body = req.body.toString("utf8");
const payload = JSON.parse(body) as { type?: string; challenge?: string };
const id = req.header("webhook-id") ?? "";
const timestamp = req.header("webhook-timestamp") ?? "";
const expected = crypto.createHmac("sha256", key).update(`${id}.${timestamp}.${body}`).digest("base64");
const digest = Buffer.from(expected);
const valid = (req.header("webhook-signature") ?? "").split(" ").some((entry) => {
const received = Buffer.from(entry.replace(/^v1,/, ""));
return received.length === digest.length && crypto.timingSafeEqual(received, digest);
});
if (!valid) return res.status(401).send("invalid signature");
// Verification challenge: echo it back.
if (payload.type === "verification") return res.json({ challenge: payload.challenge });
// Occurrence: handle it idempotently by webhook-id.
res.sendStatus(204);
});

始终对照原始身体进行验证。拒绝交货 webhook-timestamp 从现在开始,我就不再想着你了 。 ”

告诉你自己的写作分开​

通过写回对事件做出反应的代理将看到自己的写入作为事件出现。 userId 单独不能将它们分开:个人 API 密钥或 OAuth 代理作为其用户,因此代理发布的评论和发布者发布的评论具有相同的属性。 userId.

因此,每次发生都包括:

"actor": { "kind": "apiKey", "userId": "…", "self": true }
外地意义
kinduser (网络应用程序), apiKey, agent (交互式 OAuth 代理), support, automation, 或 system.
userId用户作为更改。 null 用于自动化和系统变更。
selftrue 只有当订阅连接本身进行更改时:相同的 OAuth 代理授权、直接使用的相同 API 密钥或相同的无人值守服务连接。

自动化效果报告为 automation 即使代理人自己的写入触发了它们,因此在代理人评论后添加标签的规则不会被读作代理人的写入。

该 title 以及 text 字段是用户内容,应将其视为数据,而不是代理的指令。

直接调用同一个API​

MCP 方法转发到三个公共 API 路由,这些路由接受相同的载体凭据以及相同的请求和响应主体。 从拥有 API 密钥并希望在不使用 MCP 的情况下订阅事件的集成中使用这些方法:

路线MCP法
GET /api/v1/mcp-eventsevents/list
POST /api/v1/mcp-events/subscribeevents/subscribe
POST /api/v1/mcp-events/unsubscribeevents/unsubscribe

失败将作为带有代码的公共 API 问题文档返回 MCP_EVENT_NOT_FOUND, MCP_EVENT_UNSUPPORTED, MCP_SUBSCRIPTION_LIMIT, 和 MCP_CALLBACK_ENDPOINT,MCP 层将其映射到上面的误差表。

对于应该超越任何一个连接并覆盖整个事件目录的团队集成,请使用 Webhook MCP 事件订阅属于创建它们的凭据,除非刷新,否则将在 24 小时内过期,并且仅涵盖四个卡片和评论事件以及您自己的 Up next 队列。 Webhook 不会传递 Up next 更改,因为队列是个人数据,而不是工作区数据。

故障排除​

问题要检查什么
客户端不提供活动。必须连接到托管的 /mcp 关于当前的终点 MCP 客户端中,服务器的重新扫描; stdio 通过 kanera mcp 从不提供事件。
events/subscribe 回返 -32015 与 challenge_failed.回调没有回应挑战,回应了非 2xx 状态,重定向,或者是私有、回路或元数据地址。 Kanera 故意以相同的方式报告所有这些。
-32012 Forbidden 点击订阅。身份不能看到工作区、白板或卡片。 白板访客必须通过 boardId;仅指定物品的客人必须通过指定的 cardId。会话cookie无法订阅;使用 OAuth 连接或 API 密钥。
交付停止,但订阅仍然刷新。检查 deliveryStatus.lastError 以及 failedSince 如果凭据已被撤销或用户失去了访问权限,则订阅已过期,并且刷新返回无。 deliveryStatus.
代理不断对自己的评论作出反应。检查 data.actor.self 写作之前。
-32602 订阅时 priorities.changed.空转 arguments。一个 targetUserId 或者任何其他参数被拒绝:只有您自己的队列才能被观察。
停机后错过的更改。重试在大约 7.5 分钟后停止,并且不会重播。 cards.list, cards.get, comments.list, 或 priorities.list.