@ema.co/mcp-toolkit 2026.4.9-2 → 2026.4.9-3
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/dist/mcp/guidance.js
CHANGED
|
@@ -198,7 +198,7 @@ function formatToolSection(tg) {
|
|
|
198
198
|
}
|
|
199
199
|
if (tg.toolName === "feedback") {
|
|
200
200
|
extras.push("\nYour feedback is automatically collected and analyzed to improve the toolkit for all agents.");
|
|
201
|
-
extras.push("Include `knowledge_ref` to correlate feedback with specific knowledge documents — this drives
|
|
201
|
+
extras.push("Include `knowledge_ref` to correlate feedback with specific knowledge documents — this drives both confidence scoring (MCP-side) and DE native ranking (UserEvents). All categories emit DE events: positive feedback sends conversion signals, negative feedback (correction, confusion, gap) sends zero-value conversions so DE learns what didn't work. Use `#fragment` for sub-document precision: `knowledge_ref=\"topic_auth#L51-L56\"` (lines), `knowledge_ref=\"topic_auth#authentication\"` (section).");
|
|
202
202
|
}
|
|
203
203
|
return `## ${title}
|
|
204
204
|
Use the \`${tg.toolName}\` tool${tg.quickTip ? `. ${tg.quickTip}` : ""}:
|
|
@@ -16,10 +16,8 @@ import { submitFeedback, listFeedback, listTelemetry, analyzeFeedback, rotateLog
|
|
|
16
16
|
import { markProbeResponded } from "./probes.js";
|
|
17
17
|
import { appendToOutbox, flushOutbox, getOutboxStats, readLocalMessages } from "./outbox.js";
|
|
18
18
|
import { isRemoteEnabled } from "./remote-store.js";
|
|
19
|
-
import { writeUserEvent } from "../../../knowledge/search-client.js";
|
|
20
19
|
import { stripFragment } from "../../../knowledge/pipeline/confidence.js";
|
|
21
|
-
import {
|
|
22
|
-
import { getAttributionToken } from "../knowledge/session-state.js";
|
|
20
|
+
import { emitKnowledgeUserEvent, categoryToEvent } from "../knowledge/user-events.js";
|
|
23
21
|
import { analyzeGlobal } from "./global-analysis.js";
|
|
24
22
|
import { TOOLKIT_VERSION } from "../env/config.js";
|
|
25
23
|
const VALID_CATEGORIES = ALL_CATEGORIES;
|
|
@@ -147,34 +145,13 @@ async function handleSubmit(args) {
|
|
|
147
145
|
// Best-effort — don't block feedback submission
|
|
148
146
|
}
|
|
149
147
|
}
|
|
150
|
-
// UserEvent emission: fire DE
|
|
151
|
-
//
|
|
148
|
+
// UserEvent emission: fire DE event for ALL feedback with knowledge_ref.
|
|
149
|
+
// Positive → conversion (value 1.0), negative → conversion (value 0.0), neutral → view-item.
|
|
152
150
|
// Independent of confidence loop — no guards, no cooldown. Fire-and-forget.
|
|
153
151
|
if (docId) {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
&& (qualityData?.usefulness ?? 0) >= 4;
|
|
158
|
-
const isInteraction = category === "interaction";
|
|
159
|
-
if (isSuccess || isHighQuality || isInteraction) {
|
|
160
|
-
const conversionType = isSuccess ? "knowledge-success"
|
|
161
|
-
: isHighQuality ? "knowledge-quality-high"
|
|
162
|
-
: undefined; // interaction → view-item, no conversionType
|
|
163
|
-
getOrCreateClientId()
|
|
164
|
-
.then((clientId) => {
|
|
165
|
-
const token = getAttributionToken(docId);
|
|
166
|
-
writeUserEvent({
|
|
167
|
-
eventType: conversionType ? "conversion" : "view-item",
|
|
168
|
-
userPseudoId: clientId,
|
|
169
|
-
...(token ? { attributionToken: token } : {}),
|
|
170
|
-
documents: [{
|
|
171
|
-
id: docId,
|
|
172
|
-
...(conversionType ? { conversionValue: isSuccess ? 1.0 : 0.8 } : {}),
|
|
173
|
-
}],
|
|
174
|
-
...(conversionType ? { conversionType } : {}),
|
|
175
|
-
}).catch(() => { });
|
|
176
|
-
})
|
|
177
|
-
.catch(() => { });
|
|
152
|
+
const eventOpts = categoryToEvent(category, qualityData);
|
|
153
|
+
if (eventOpts) {
|
|
154
|
+
emitKnowledgeUserEvent({ ...eventOpts, docIds: [docId] });
|
|
178
155
|
}
|
|
179
156
|
}
|
|
180
157
|
return {
|
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { getConsultedDocs } from "./session-state.js";
|
|
22
22
|
import { processConfidenceFeedback } from "./confidence-loop.js";
|
|
23
|
-
import {
|
|
24
|
-
import { getOrCreateClientId } from "../feedback/client-id.js";
|
|
23
|
+
import { emitKnowledgeUserEvent } from "./user-events.js";
|
|
25
24
|
/** Weight multiplier per signal layer */
|
|
26
25
|
const LAYER_WEIGHTS = {
|
|
27
26
|
system: 0.1, // API accepted it — weakest signal
|
|
@@ -178,27 +177,17 @@ export async function emitOutcomeFeedback(event) {
|
|
|
178
177
|
}
|
|
179
178
|
}
|
|
180
179
|
}
|
|
181
|
-
// Fire UserEvents (
|
|
182
|
-
|
|
183
|
-
.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
...(isConversion ? { conversionValue } : {}),
|
|
193
|
-
}));
|
|
194
|
-
writeUserEvent({
|
|
195
|
-
eventType: isConversion ? "conversion" : "view-item",
|
|
196
|
-
userPseudoId: clientId,
|
|
197
|
-
...(isConversion ? { conversionType: eventType } : {}),
|
|
198
|
-
documents,
|
|
199
|
-
}).catch(() => { });
|
|
200
|
-
})
|
|
201
|
-
.catch(() => { });
|
|
180
|
+
// Fire UserEvents via centralized emitter (handles attribution tokens + fragment stripping)
|
|
181
|
+
const baseValue = outcome.quality === "success" ? 1.0
|
|
182
|
+
: outcome.quality === "partial" ? 0.5
|
|
183
|
+
: 0;
|
|
184
|
+
const conversionValue = baseValue * weight;
|
|
185
|
+
emitKnowledgeUserEvent({
|
|
186
|
+
docIds: [...docs],
|
|
187
|
+
signal: outcome.isPositive ? "positive" : "negative",
|
|
188
|
+
conversionType: eventType,
|
|
189
|
+
conversionValue,
|
|
190
|
+
});
|
|
202
191
|
console.error(`[OUTCOME-FEEDBACK] ${eventType} (layer=${layer}, weight=${weight}): ` +
|
|
203
192
|
`${docs.size} consulted docs, ${updates} confidence updates`);
|
|
204
193
|
return { docs_processed: docs.size, confidence_updates: updates, event_type: eventType };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized DE UserEvent emission for knowledge feedback signals.
|
|
3
|
+
*
|
|
4
|
+
* Single entry point for all feedback → DE UserEvent emission.
|
|
5
|
+
* Handles: attribution token lookup, fragment stripping, signal→event mapping,
|
|
6
|
+
* and fire-and-forget delivery.
|
|
7
|
+
*
|
|
8
|
+
* Ownership boundary:
|
|
9
|
+
* - MCP OWNS: interaction/feedback collection, signal classification
|
|
10
|
+
* - knowledge-service WILL OWN: DE transport, attribution, event schema
|
|
11
|
+
*
|
|
12
|
+
* TODO(knowledge-service): migrate DE transport when knowledge-service owns DE integration.
|
|
13
|
+
* MCP will continue to own feedback collection and signal classification.
|
|
14
|
+
* knowledge-service will own: writeUserEvent, attribution cache, event→DE mapping.
|
|
15
|
+
*/
|
|
16
|
+
import { writeUserEvent } from "../../../knowledge/search-client.js";
|
|
17
|
+
import { getOrCreateClientId } from "../feedback/client-id.js";
|
|
18
|
+
import { getAttributionToken } from "./session-state.js";
|
|
19
|
+
import { stripFragment } from "../../../knowledge/pipeline/confidence.js";
|
|
20
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
// Feedback category → signal mapping
|
|
22
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
// TODO(knowledge-service): signal classification stays in MCP (owns feedback semantics)
|
|
24
|
+
const CATEGORY_EVENT_MAP = {
|
|
25
|
+
// Positive signals
|
|
26
|
+
success: { signal: "positive", conversionType: "knowledge-success", value: 1.0 },
|
|
27
|
+
interaction: { signal: "neutral", conversionType: "", value: 0 },
|
|
28
|
+
// Negative signals
|
|
29
|
+
correction: { signal: "negative", conversionType: "knowledge-correction", value: 0.0 },
|
|
30
|
+
confusion: { signal: "negative", conversionType: "knowledge-confusion", value: 0.0 },
|
|
31
|
+
gap: { signal: "negative", conversionType: "knowledge-gap", value: 0.0 },
|
|
32
|
+
error_unclear: { signal: "negative", conversionType: "knowledge-error", value: 0.0 },
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Map a feedback category + quality data to UserEvent parameters.
|
|
36
|
+
* Returns undefined for categories that shouldn't emit events (e.g., suggestion, probe_response).
|
|
37
|
+
*/
|
|
38
|
+
export function categoryToEvent(category, qualityData) {
|
|
39
|
+
// Quality category: only emit for high-quality ratings
|
|
40
|
+
if (category === "quality") {
|
|
41
|
+
const isHighQuality = (qualityData?.accuracy ?? 0) >= 4
|
|
42
|
+
&& (qualityData?.usefulness ?? 0) >= 4;
|
|
43
|
+
if (!isHighQuality)
|
|
44
|
+
return undefined;
|
|
45
|
+
return {
|
|
46
|
+
docIds: [], // caller fills in
|
|
47
|
+
signal: "positive",
|
|
48
|
+
conversionType: "knowledge-quality-high",
|
|
49
|
+
conversionValue: 0.8,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const mapped = CATEGORY_EVENT_MAP[category];
|
|
53
|
+
if (!mapped)
|
|
54
|
+
return undefined;
|
|
55
|
+
return {
|
|
56
|
+
docIds: [], // caller fills in
|
|
57
|
+
signal: mapped.signal,
|
|
58
|
+
conversionType: mapped.conversionType || undefined,
|
|
59
|
+
conversionValue: mapped.value,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
63
|
+
// Centralized emitter
|
|
64
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
65
|
+
/**
|
|
66
|
+
* Emit a DE UserEvent for knowledge feedback signals.
|
|
67
|
+
*
|
|
68
|
+
* Handles attribution token lookup, fragment stripping, and fire-and-forget delivery.
|
|
69
|
+
* All feedback paths (explicit feedback, outcome feedback) should use this function
|
|
70
|
+
* instead of calling writeUserEvent directly.
|
|
71
|
+
*
|
|
72
|
+
* TODO(knowledge-service): DE transport (writeUserEvent, attribution) moves to shared lib.
|
|
73
|
+
* This function becomes: knowledgeService.emitFeedbackEvent({ docIds, signal, ... })
|
|
74
|
+
*/
|
|
75
|
+
export function emitKnowledgeUserEvent(opts) {
|
|
76
|
+
if (opts.docIds.length === 0)
|
|
77
|
+
return;
|
|
78
|
+
const isConversion = opts.signal !== "neutral";
|
|
79
|
+
const conversionValue = opts.conversionValue
|
|
80
|
+
?? (opts.signal === "positive" ? 1.0 : opts.signal === "negative" ? 0.0 : undefined);
|
|
81
|
+
getOrCreateClientId()
|
|
82
|
+
.then((clientId) => {
|
|
83
|
+
// Strip fragments and dedupe doc IDs
|
|
84
|
+
const seen = new Set();
|
|
85
|
+
const documents = [];
|
|
86
|
+
let attributionToken;
|
|
87
|
+
for (const rawId of opts.docIds) {
|
|
88
|
+
const docId = stripFragment(rawId);
|
|
89
|
+
if (!docId || seen.has(docId))
|
|
90
|
+
continue;
|
|
91
|
+
seen.add(docId);
|
|
92
|
+
// Use first available attribution token (all docs from same search share one)
|
|
93
|
+
if (!attributionToken) {
|
|
94
|
+
attributionToken = getAttributionToken(docId);
|
|
95
|
+
}
|
|
96
|
+
documents.push({
|
|
97
|
+
id: docId,
|
|
98
|
+
...(isConversion && conversionValue !== undefined ? { conversionValue } : {}),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (documents.length === 0)
|
|
102
|
+
return;
|
|
103
|
+
writeUserEvent({
|
|
104
|
+
eventType: isConversion ? "conversion" : "view-item",
|
|
105
|
+
userPseudoId: clientId,
|
|
106
|
+
...(attributionToken ? { attributionToken } : {}),
|
|
107
|
+
...(isConversion && opts.conversionType ? { conversionType: opts.conversionType } : {}),
|
|
108
|
+
documents,
|
|
109
|
+
}).catch(() => { });
|
|
110
|
+
})
|
|
111
|
+
.catch(() => { });
|
|
112
|
+
}
|
|
@@ -692,13 +692,22 @@ The toolkit also collects anonymous telemetry (tool usage, error rates, latency)
|
|
|
692
692
|
|
|
693
693
|
## Knowledge Confidence Scoring
|
|
694
694
|
|
|
695
|
-
Feedback drives
|
|
696
|
-
- knowledge_ref="<document_id>" — top-level parameter, triggers real-time confidence update
|
|
697
|
-
- Negative categories (downgrade score): gap, confusion, correction, error_unclear
|
|
698
|
-
- Positive categories (upgrade score): success, interaction
|
|
699
|
-
- Nuanced: quality (depends on accuracy/usefulness scores — <3 negative, >=4 positive, no scores = neutral)
|
|
695
|
+
Feedback drives two parallel DE ranking signals when knowledge_ref is provided:
|
|
700
696
|
|
|
701
|
-
|
|
697
|
+
1. **Confidence Loop (MCP-side)** — immediate structData.confidence_score update via PATCH
|
|
698
|
+
2. **DE UserEvents (native ranking)** — conversion/view-item events for DE's ML-based ranking
|
|
699
|
+
|
|
700
|
+
All feedback categories emit DE UserEvents:
|
|
701
|
+
- Positive (conversion, value=1.0): success, interaction
|
|
702
|
+
- Negative (conversion, value=0.0): correction, confusion, gap, error_unclear
|
|
703
|
+
- Quality: depends on accuracy/usefulness scores (>=4 both → positive at 0.8, else skipped)
|
|
704
|
+
- Neutral (view-item): interaction
|
|
705
|
+
|
|
706
|
+
Include knowledge_ref to correlate feedback with specific documents:
|
|
707
|
+
- knowledge_ref="<document_id>" — triggers both confidence update and DE event
|
|
708
|
+
- Use #fragment for sub-document precision: knowledge_ref="topic_auth#L51-L56"
|
|
709
|
+
|
|
710
|
+
Labels derived from confidence score thresholds:
|
|
702
711
|
- verified (>= 0.80) — no significant negative feedback
|
|
703
712
|
- inferred (0.50–0.79) — some negative feedback received
|
|
704
713
|
- low-confidence (< 0.50) — multiple negative reports, auto-downgraded in search
|
package/package.json
CHANGED