@emseepea/feedback 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -0
- package/dist/mcp-events.d.ts +15 -0
- package/dist/mcp-events.d.ts.map +1 -0
- package/dist/mcp-events.js +57 -0
- package/dist/mcp-events.js.map +1 -0
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -241,6 +241,37 @@ and cannot change the MCP result. Hook code must honor that signal and deadline.
|
|
|
241
241
|
For reliable delivery, use the PostgreSQL or Firestore outbox, or GitHub and
|
|
242
242
|
Zendesk native automation.
|
|
243
243
|
|
|
244
|
+
### Notify an AI client about a team reply
|
|
245
|
+
|
|
246
|
+
A protected conversation can optionally use MCP Events to tell a subscribed AI
|
|
247
|
+
client that a team reply is ready. The event carries only the account or client
|
|
248
|
+
scope and stable thread, message, and source-event IDs. It does not carry the
|
|
249
|
+
reply text. The client should call `get-feedback-thread` and present the reply
|
|
250
|
+
to the person.
|
|
251
|
+
|
|
252
|
+
1. Configure `createFeedbackReplyEventsOptions` from
|
|
253
|
+
`@emseepea/feedback/mcp-events` as the server's `events` option. Use the same
|
|
254
|
+
stable account or client scope for its `ownerKey` as for
|
|
255
|
+
`defineFeedbackConversation({ scope })`.
|
|
256
|
+
2. Supply `canReadThread(ownerKey, threadId)` to check access to the exact
|
|
257
|
+
thread. The check runs when subscribing and again before delivery. Supply
|
|
258
|
+
your durable MCP Events store and health check as described in the
|
|
259
|
+
[MCP Events guide](https://emseepea.github.io/emseepea/mcp-events/).
|
|
260
|
+
3. After saving a team reply, pass its typed feedback event to
|
|
261
|
+
`publishFeedbackTeamReplyEvent(app, event)`. For PostgreSQL or Firestore,
|
|
262
|
+
publish from the transactional outbox: a saved queue that can be retried
|
|
263
|
+
after failures. For GitHub or Zendesk, use the checked provider webhook.
|
|
264
|
+
The helper ignores non-team events and publishes only a reference to a team
|
|
265
|
+
message.
|
|
266
|
+
|
|
267
|
+
An outbox retry can publish the same reply more than once. Use the stable
|
|
268
|
+
`sourceEventId` in the event payload to recognise duplicates.
|
|
269
|
+
|
|
270
|
+
The Markdown destination is one-way and has no team reply to announce. An
|
|
271
|
+
event delivery, or a reply being offered to an AI client, does not prove a
|
|
272
|
+
person saw it. Test the complete reply journey in your own client and
|
|
273
|
+
deployment before relying on it.
|
|
274
|
+
|
|
244
275
|
## Effect and Retry Limits
|
|
245
276
|
|
|
246
277
|
Creating a thread, submitting feedback, and appending a message are
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { publishMcpEvent, type McpEventsOptions } from "@emseepea/server";
|
|
2
|
+
export declare const feedbackReplyEventName = "feedback.reply.ready";
|
|
3
|
+
export interface FeedbackReplyEventsOptions {
|
|
4
|
+
/** Use the same stable scope projection as defineFeedbackConversation. */
|
|
5
|
+
readonly ownerKey: McpEventsOptions["ownerKey"];
|
|
6
|
+
/** Check access to this exact thread, including when a delivery is due. */
|
|
7
|
+
readonly canReadThread: (ownerKey: string, threadId: string) => boolean | Promise<boolean>;
|
|
8
|
+
readonly health: McpEventsOptions["health"];
|
|
9
|
+
readonly store: McpEventsOptions["store"];
|
|
10
|
+
readonly callbackTimeoutMs?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function createFeedbackReplyEventsOptions(options: FeedbackReplyEventsOptions): McpEventsOptions;
|
|
13
|
+
/** Publish a durable team reply reference after the feedback backend commits it. */
|
|
14
|
+
export declare function publishFeedbackTeamReplyEvent(app: Parameters<typeof publishMcpEvent>[0], event: unknown): Promise<boolean>;
|
|
15
|
+
//# sourceMappingURL=mcp-events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-events.d.ts","sourceRoot":"","sources":["../src/mcp-events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAI1E,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAa7D,MAAM,WAAW,0BAA0B;IACzC,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAChD,2EAA2E;IAC3E,QAAQ,CAAC,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3F,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC5C,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC1C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,0BAA0B,GAAG,gBAAgB,CAuBtG;AAED,oFAAoF;AACpF,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,CAAC,EAC1C,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,OAAO,CAAC,CAYlB"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { publishMcpEvent } from "@emseepea/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { feedbackEventSchema } from "./index.js";
|
|
4
|
+
export const feedbackReplyEventName = "feedback.reply.ready";
|
|
5
|
+
const identifier = z.string().min(1).max(240);
|
|
6
|
+
const threadArguments = z.strictObject({
|
|
7
|
+
threadId: identifier.describe("Exact feedback thread to watch for a team reply."),
|
|
8
|
+
});
|
|
9
|
+
const replyReference = z.strictObject({
|
|
10
|
+
scope: identifier,
|
|
11
|
+
threadId: identifier,
|
|
12
|
+
messageId: identifier,
|
|
13
|
+
sourceEventId: identifier,
|
|
14
|
+
});
|
|
15
|
+
export function createFeedbackReplyEventsOptions(options) {
|
|
16
|
+
if (typeof options?.canReadThread !== "function")
|
|
17
|
+
throw new TypeError("feedback reply events require canReadThread");
|
|
18
|
+
return {
|
|
19
|
+
definitions: [{
|
|
20
|
+
name: feedbackReplyEventName,
|
|
21
|
+
description: "A team reply is ready. Read the exact thread with get-feedback-thread and present the reply to the user.",
|
|
22
|
+
inputSchema: threadArguments,
|
|
23
|
+
payloadSchema: replyReference,
|
|
24
|
+
matches: (arguments_, data, ownerKey) => arguments_.threadId === data.threadId && ownerKey === data.scope,
|
|
25
|
+
}],
|
|
26
|
+
ownerKey: options.ownerKey,
|
|
27
|
+
async authorize({ ownerKey, name, arguments: arguments_, phase }) {
|
|
28
|
+
if (name !== feedbackReplyEventName)
|
|
29
|
+
return false;
|
|
30
|
+
if (phase === "list")
|
|
31
|
+
return true;
|
|
32
|
+
const parsed = threadArguments.safeParse(arguments_);
|
|
33
|
+
return parsed.success && await options.canReadThread(ownerKey, parsed.data.threadId);
|
|
34
|
+
},
|
|
35
|
+
health: options.health,
|
|
36
|
+
store: options.store,
|
|
37
|
+
...(options.callbackTimeoutMs === undefined ? {} : { callbackTimeoutMs: options.callbackTimeoutMs }),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Publish a durable team reply reference after the feedback backend commits it. */
|
|
41
|
+
export async function publishFeedbackTeamReplyEvent(app, event) {
|
|
42
|
+
const checked = feedbackEventSchema.safeParse(event);
|
|
43
|
+
if (!checked.success)
|
|
44
|
+
throw new TypeError("Invalid feedback event");
|
|
45
|
+
if (checked.data.type !== "feedback.message.added" || checked.data.author !== "team")
|
|
46
|
+
return false;
|
|
47
|
+
if (!checked.data.messageId)
|
|
48
|
+
throw new TypeError("Team reply event requires messageId");
|
|
49
|
+
await publishMcpEvent(app, feedbackReplyEventName, replyReference.parse({
|
|
50
|
+
scope: checked.data.scope,
|
|
51
|
+
threadId: checked.data.threadId,
|
|
52
|
+
messageId: checked.data.messageId,
|
|
53
|
+
sourceEventId: checked.data.id,
|
|
54
|
+
}));
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=mcp-events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-events.js","sourceRoot":"","sources":["../src/mcp-events.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emseepea/feedback",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Optional detailed feedback and support conversations for Em See Pea servers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -44,6 +44,10 @@
|
|
|
44
44
|
"types": "./dist/webhooks.d.ts",
|
|
45
45
|
"import": "./dist/webhooks.js"
|
|
46
46
|
},
|
|
47
|
+
"./mcp-events": {
|
|
48
|
+
"types": "./dist/mcp-events.d.ts",
|
|
49
|
+
"import": "./dist/mcp-events.js"
|
|
50
|
+
},
|
|
47
51
|
"./testing-server": "./dist/testing-server.js"
|
|
48
52
|
},
|
|
49
53
|
"files": [
|
|
@@ -55,18 +59,18 @@
|
|
|
55
59
|
"build": "tsc -p tsconfig.json && tsc -p tsconfig.type-tests.json",
|
|
56
60
|
"prepack": "npm run build",
|
|
57
61
|
"test": "npm run build && npm run test:built",
|
|
58
|
-
"test:built": "node --test test/*.test.mjs",
|
|
62
|
+
"test:built": "node --test test/*.test.mjs src/*.test.js",
|
|
59
63
|
"test:postgres": "cd ../../examples/multi-instance-postgres-server && node test/with-postgres.mjs node --test ../../packages/feedback/test/postgres.integration.mjs",
|
|
60
64
|
"test:llm": "npm run build && npm run test:llm:built",
|
|
61
65
|
"test:llm:built": "emseepea-test eval",
|
|
62
66
|
"lint": "oxlint src test eval"
|
|
63
67
|
},
|
|
64
68
|
"dependencies": {
|
|
65
|
-
"@emseepea/server": "0.21.
|
|
69
|
+
"@emseepea/server": "0.21.1",
|
|
66
70
|
"zod": "4.4.3"
|
|
67
71
|
},
|
|
68
72
|
"devDependencies": {
|
|
69
|
-
"@emseepea/testing": "0.20.
|
|
73
|
+
"@emseepea/testing": "0.20.3",
|
|
70
74
|
"pg": "8.23.0"
|
|
71
75
|
},
|
|
72
76
|
"engines": {
|