@posthog/mcp 0.15.0 → 0.16.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.
Files changed (59) hide show
  1. package/README.md +64 -0
  2. package/dist/extensions/constants.d.ts +9 -0
  3. package/dist/extensions/constants.d.ts.map +1 -1
  4. package/dist/extensions/constants.js +9 -0
  5. package/dist/extensions/constants.mjs +9 -0
  6. package/dist/extensions/event-types.d.ts +1 -0
  7. package/dist/extensions/event-types.d.ts.map +1 -1
  8. package/dist/extensions/event-types.js +1 -0
  9. package/dist/extensions/event-types.mjs +1 -0
  10. package/dist/extensions/feedback.d.ts +41 -0
  11. package/dist/extensions/feedback.d.ts.map +1 -0
  12. package/dist/extensions/feedback.js +259 -0
  13. package/dist/extensions/feedback.mjs +200 -0
  14. package/dist/extensions/instrument-highlevel.d.ts.map +1 -1
  15. package/dist/extensions/instrument-highlevel.js +19 -1
  16. package/dist/extensions/instrument-highlevel.mjs +20 -2
  17. package/dist/extensions/instrument-lowlevel.d.ts.map +1 -1
  18. package/dist/extensions/instrument-lowlevel.js +19 -1
  19. package/dist/extensions/instrument-lowlevel.mjs +20 -2
  20. package/dist/extensions/instrumentation.d.ts +30 -13
  21. package/dist/extensions/instrumentation.d.ts.map +1 -1
  22. package/dist/extensions/instrumentation.js +27 -7
  23. package/dist/extensions/instrumentation.mjs +27 -7
  24. package/dist/extensions/mcp-payloads.d.ts +9 -3
  25. package/dist/extensions/mcp-payloads.d.ts.map +1 -1
  26. package/dist/extensions/mcp-payloads.js +16 -7
  27. package/dist/extensions/mcp-payloads.mjs +11 -5
  28. package/dist/extensions/posthog-events.d.ts.map +1 -1
  29. package/dist/extensions/posthog-events.js +1 -0
  30. package/dist/extensions/posthog-events.mjs +1 -0
  31. package/dist/extensions/posthog-mcp.d.ts +23 -3
  32. package/dist/extensions/posthog-mcp.d.ts.map +1 -1
  33. package/dist/extensions/posthog-mcp.js +27 -1
  34. package/dist/extensions/posthog-mcp.mjs +27 -1
  35. package/dist/extensions/sanitization.js +1 -1
  36. package/dist/extensions/sanitization.mjs +2 -2
  37. package/dist/index.d.ts +2 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +10 -0
  40. package/dist/index.mjs +5 -1
  41. package/dist/types.d.ts +113 -0
  42. package/dist/types.d.ts.map +1 -1
  43. package/dist/version.d.ts +1 -1
  44. package/dist/version.js +1 -1
  45. package/dist/version.mjs +1 -1
  46. package/package.json +4 -4
  47. package/src/extensions/constants.ts +9 -0
  48. package/src/extensions/event-types.ts +1 -0
  49. package/src/extensions/feedback.ts +349 -0
  50. package/src/extensions/instrument-highlevel.ts +39 -2
  51. package/src/extensions/instrument-lowlevel.ts +39 -2
  52. package/src/extensions/instrumentation.ts +65 -17
  53. package/src/extensions/mcp-payloads.ts +19 -6
  54. package/src/extensions/posthog-events.ts +1 -0
  55. package/src/extensions/posthog-mcp.ts +74 -3
  56. package/src/extensions/sanitization.ts +3 -3
  57. package/src/index.ts +21 -0
  58. package/src/types.ts +119 -0
  59. package/src/version.ts +1 -1
@@ -0,0 +1,200 @@
1
+ import { PostHogMCPAnalyticsProperty } from "./constants.mjs";
2
+ import { log } from "./logger.mjs";
3
+ import { sanitizeFreeText, sanitizeFreeTextValue } from "./mcp-payloads.mjs";
4
+ const SEND_FEEDBACK_TOOL_NAME = 'send_feedback';
5
+ const FEEDBACK_TYPES = [
6
+ 'missing_capability',
7
+ 'issue',
8
+ 'praise',
9
+ 'other'
10
+ ];
11
+ const SENTIMENTS = [
12
+ 'positive',
13
+ 'neutral',
14
+ 'negative',
15
+ 'mixed'
16
+ ];
17
+ const MAX_FEEDBACK_TEXT_LENGTH = 2048;
18
+ const MAX_FEEDBACK_TOOL_NAME_LENGTH = 256;
19
+ const TRUNCATION_SUFFIX = '...';
20
+ const DEFAULT_FEEDBACK_DESCRIPTION = 'Send feedback about this server to its developers. Most important: report a missing capability whenever no available tool fits your task, even if you can work around it (feedback_type "missing_capability"). Also welcome: a tool that failed or confused you, an unhelpful error, or something that worked well. This records the feedback; it does not add or change tools. Do not include user PII or sensitive content.';
21
+ const CORE_FEEDBACK_SCHEMA_PROPERTIES = {
22
+ feedback_type: {
23
+ type: 'string',
24
+ enum: [
25
+ ...FEEDBACK_TYPES
26
+ ],
27
+ description: "What kind of feedback this is. Use 'missing_capability' when the tool you needed does not exist in the tool list - nothing failed, the capability is absent (this is the most valuable report; send it even if you found a workaround). Use 'issue' when an existing tool behaved badly: it failed, returned a confusing error, or its description or schema misled you. Use 'praise' when something worked notably well. Use 'other' for anything else."
28
+ },
29
+ summary: {
30
+ type: 'string',
31
+ description: "One self-contained sentence. For 'missing_capability': the capability you needed, e.g. 'No tool to delete multiple cohorts in one call.' For 'issue': the tool and the problem, e.g. 'query-trends rejects relative date ranges with an unclear error.'"
32
+ },
33
+ details: {
34
+ type: 'string',
35
+ description: "Optional longer context: what you tried, exact parameter values, the error text you saw, and any workaround you used. Omit when the summary says it all."
36
+ },
37
+ friction_points: {
38
+ type: 'string',
39
+ description: "Optional: the specific moments that slowed you down, as short bullet-like sentences, quoting exact tool names, parameters, or error text."
40
+ },
41
+ suggested_improvement: {
42
+ type: 'string',
43
+ description: "Optional: the concrete change that would have helped, e.g. the tool to add, the description to reword, or the error message to improve."
44
+ },
45
+ tool_name: {
46
+ type: 'string',
47
+ description: "Optional: the existing tool this feedback is about (for 'issue' or 'praise'). Leave empty for 'missing_capability' - the point is that no tool fits."
48
+ },
49
+ sentiment: {
50
+ type: 'string',
51
+ enum: [
52
+ ...SENTIMENTS
53
+ ],
54
+ description: 'Optional: how the experience felt overall.'
55
+ },
56
+ task_completed: {
57
+ type: 'boolean',
58
+ description: "Optional: whether you still completed the user's task despite the problem."
59
+ }
60
+ };
61
+ const RESERVED_EXTRA_PROPERTY_KEYS = new Set([
62
+ ...Object.keys(CORE_FEEDBACK_SCHEMA_PROPERTIES),
63
+ 'type',
64
+ 'tool',
65
+ 'context',
66
+ 'conversation_id',
67
+ 'llm_model'
68
+ ]);
69
+ function resolveCollectFeedbackOptions(config) {
70
+ if (!config) return;
71
+ return true === config ? {} : config;
72
+ }
73
+ function getFeedbackToolDescriptor(options = {}) {
74
+ const extraProperties = options.extraProperties ?? {};
75
+ const extraRequired = options.extraRequired ?? [];
76
+ for (const key of Object.keys(extraProperties))if (RESERVED_EXTRA_PROPERTY_KEYS.has(key)) throw new Error(`collectFeedback.extraProperties key "${key}" collides with a core or SDK-reserved send_feedback field. Rename it.`);
77
+ for (const key of extraRequired)if (!Object.prototype.hasOwnProperty.call(extraProperties, key)) throw new Error(`collectFeedback.extraRequired key "${key}" is not declared in extraProperties.`);
78
+ const copiedExtras = Object.keys(extraProperties).length > 0 ? JSON.parse(JSON.stringify(extraProperties)) : void 0;
79
+ return {
80
+ name: options.toolName ?? SEND_FEEDBACK_TOOL_NAME,
81
+ description: options.description ?? DEFAULT_FEEDBACK_DESCRIPTION,
82
+ inputSchema: {
83
+ type: 'object',
84
+ properties: {
85
+ ...JSON.parse(JSON.stringify(CORE_FEEDBACK_SCHEMA_PROPERTIES)),
86
+ ...copiedExtras
87
+ },
88
+ required: [
89
+ 'feedback_type',
90
+ 'summary',
91
+ ...extraRequired
92
+ ]
93
+ },
94
+ annotations: {
95
+ title: 'Send feedback',
96
+ readOnlyHint: true,
97
+ openWorldHint: true,
98
+ idempotentHint: true,
99
+ destructiveHint: false
100
+ }
101
+ };
102
+ }
103
+ function readString(value) {
104
+ return 'string' == typeof value && value.trim() ? value : void 0;
105
+ }
106
+ function parseFeedbackType(value) {
107
+ return FEEDBACK_TYPES.includes(value) ? value : 'other';
108
+ }
109
+ function parseSentiment(value) {
110
+ return SENTIMENTS.includes(value) ? value : void 0;
111
+ }
112
+ function matchesExtraSchema(value, schema) {
113
+ const type = Array.isArray(value) ? 'array' : null === value ? 'null' : typeof value;
114
+ if (schema.type !== type && !('integer' === schema.type && 'number' == typeof value)) return false;
115
+ return !Array.isArray(schema.enum) || schema.enum.includes(value);
116
+ }
117
+ function parseFeedbackReport(args, options = {}) {
118
+ const raw = args ?? {};
119
+ const extras = {};
120
+ for (const [key, schema] of Object.entries(options.extraProperties ?? {}))if (void 0 !== raw[key] && matchesExtraSchema(raw[key], schema)) extras[key] = raw[key];
121
+ return {
122
+ feedbackType: parseFeedbackType(raw.feedback_type),
123
+ summary: readString(raw.summary) ?? '',
124
+ sentiment: parseSentiment(raw.sentiment),
125
+ frictionPoints: readString(raw.friction_points),
126
+ suggestedImprovement: readString(raw.suggested_improvement),
127
+ details: readString(raw.details),
128
+ toolName: readString(raw.tool_name),
129
+ taskCompleted: 'boolean' == typeof raw.task_completed ? raw.task_completed : void 0,
130
+ extras,
131
+ raw
132
+ };
133
+ }
134
+ function buildFeedbackIntent(report) {
135
+ return [
136
+ report.summary,
137
+ report.details
138
+ ].filter(Boolean).join('\n\n');
139
+ }
140
+ function truncateFeedbackText(value, maxLength) {
141
+ return value.length > maxLength ? value.slice(0, maxLength) + TRUNCATION_SUFFIX : value;
142
+ }
143
+ function captureFreeText(value) {
144
+ return truncateFeedbackText(sanitizeFreeText(value), MAX_FEEDBACK_TEXT_LENGTH);
145
+ }
146
+ function captureExtraValue(value) {
147
+ const sanitized = sanitizeFreeTextValue(value);
148
+ if ('string' == typeof sanitized) return truncateFeedbackText(sanitized, MAX_FEEDBACK_TEXT_LENGTH);
149
+ if (null == sanitized || 'number' == typeof sanitized || 'boolean' == typeof sanitized) return sanitized;
150
+ try {
151
+ return truncateFeedbackText(JSON.stringify(sanitized), MAX_FEEDBACK_TEXT_LENGTH);
152
+ } catch {
153
+ return;
154
+ }
155
+ }
156
+ function buildFeedbackEventProperties(report) {
157
+ const properties = {
158
+ [PostHogMCPAnalyticsProperty.FeedbackType]: report.feedbackType
159
+ };
160
+ if (report.summary) properties[PostHogMCPAnalyticsProperty.FeedbackSummary] = captureFreeText(report.summary);
161
+ if (report.sentiment) properties[PostHogMCPAnalyticsProperty.FeedbackSentiment] = report.sentiment;
162
+ if (report.frictionPoints) properties[PostHogMCPAnalyticsProperty.FeedbackFrictionPoints] = captureFreeText(report.frictionPoints);
163
+ if (report.suggestedImprovement) properties[PostHogMCPAnalyticsProperty.FeedbackSuggestedImprovement] = captureFreeText(report.suggestedImprovement);
164
+ if (report.details) properties[PostHogMCPAnalyticsProperty.FeedbackDetails] = captureFreeText(report.details);
165
+ if (report.toolName) properties[PostHogMCPAnalyticsProperty.FeedbackTool] = truncateFeedbackText(sanitizeFreeText(report.toolName), MAX_FEEDBACK_TOOL_NAME_LENGTH);
166
+ if (void 0 !== report.taskCompleted) properties[PostHogMCPAnalyticsProperty.FeedbackTaskCompleted] = report.taskCompleted;
167
+ for (const [key, value] of Object.entries(report.extras)){
168
+ const captured = captureExtraValue(value);
169
+ if (void 0 !== captured) properties[`$mcp_feedback_${key}`] = captured;
170
+ }
171
+ return properties;
172
+ }
173
+ function sendFeedbackResult() {
174
+ return {
175
+ content: [
176
+ {
177
+ type: 'text',
178
+ text: "Your feedback was recorded for the server's developers. No additional tools are available - continue with the tools already listed."
179
+ }
180
+ ]
181
+ };
182
+ }
183
+ async function handleFeedback(report, options = {}, logger = log) {
184
+ logger(`Agent feedback reported (${report.feedbackType})`);
185
+ if (options.onFeedback) try {
186
+ const reply = await options.onFeedback(report);
187
+ if ('string' == typeof reply && reply.trim()) return {
188
+ content: [
189
+ {
190
+ type: 'text',
191
+ text: reply
192
+ }
193
+ ]
194
+ };
195
+ } catch (error) {
196
+ logger(`Warning: onFeedback handler threw; returning the default acknowledgement - ${error}`);
197
+ }
198
+ return sendFeedbackResult();
199
+ }
200
+ export { SEND_FEEDBACK_TOOL_NAME, buildFeedbackEventProperties, buildFeedbackIntent, getFeedbackToolDescriptor, handleFeedback, parseFeedbackReport, resolveCollectFeedbackOptions, sendFeedbackResult };
@@ -1 +1 @@
1
- {"version":3,"file":"instrument-highlevel.d.ts","sourceRoot":"","sources":["../../src/extensions/instrument-highlevel.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAGV,sBAAsB,EAIvB,MAAM,UAAU,CAAA;AAUjB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAoQxC,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,sBAAsB,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CA+BhG"}
1
+ {"version":3,"file":"instrument-highlevel.d.ts","sourceRoot":"","sources":["../../src/extensions/instrument-highlevel.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAGV,sBAAsB,EAIvB,MAAM,UAAU,CAAA;AAmBjB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAgSxC,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,sBAAsB,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CA+BhG"}
@@ -30,6 +30,7 @@ __webpack_require__.r(__webpack_exports__);
30
30
  __webpack_require__.d(__webpack_exports__, {
31
31
  instrumentHighLevelServer: ()=>instrumentHighLevelServer
32
32
  });
33
+ const external_feedback_js_namespaceObject = require("./feedback.js");
33
34
  const external_analytics_parameters_js_namespaceObject = require("./analytics-parameters.js");
34
35
  const external_context_parameters_js_namespaceObject = require("./context-parameters.js");
35
36
  const external_model_parameters_js_namespaceObject = require("./model-parameters.js");
@@ -171,12 +172,29 @@ async function handleToolCallRequest(highLevelServer, server, originalCallToolHa
171
172
  extra,
172
173
  eventType: external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpMissingCapability,
173
174
  explicitContextIntent: context,
174
- parameterOwnership: (0, external_instrumentation_js_namespaceObject.getVirtualToolParameterOwnership)(data, toolName),
175
+ parameterOwnership: (0, external_instrumentation_js_namespaceObject.getVirtualToolParameterOwnership)(data, toolName, (0, external_tools_js_namespaceObject.getReportMissingToolDescriptor)(toolName).inputSchema),
175
176
  execute: async ()=>(0, external_tools_js_namespaceObject.handleReportMissing)({
176
177
  context
177
178
  }, data.logger)
178
179
  });
179
180
  }
181
+ const feedbackOptions = (0, external_feedback_js_namespaceObject.resolveCollectFeedbackOptions)(data.options.collectFeedback);
182
+ const isFeedbackCandidate = void 0 !== feedbackOptions && toolName === (feedbackOptions.toolName ?? external_feedback_js_namespaceObject.SEND_FEEDBACK_TOOL_NAME);
183
+ if (isFeedbackCandidate && await (0, external_instrumentation_js_namespaceObject.isToolAdvertised)(server, toolName, extra, data.logger) === false) {
184
+ const report = (0, external_feedback_js_namespaceObject.parseFeedbackReport)(request.params?.arguments, feedbackOptions);
185
+ return await (0, external_instrumentation_js_namespaceObject.captureToolCall)({
186
+ server,
187
+ data,
188
+ request,
189
+ extra,
190
+ eventType: external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpFeedback,
191
+ explicitContextIntent: (0, external_feedback_js_namespaceObject.buildFeedbackIntent)(report),
192
+ omitCapturedParameters: true,
193
+ extraEventProperties: (0, external_feedback_js_namespaceObject.buildFeedbackEventProperties)(report),
194
+ parameterOwnership: (0, external_instrumentation_js_namespaceObject.getVirtualToolParameterOwnership)(data, toolName, (0, external_feedback_js_namespaceObject.getFeedbackToolDescriptor)(feedbackOptions).inputSchema),
195
+ execute: async ()=>(0, external_feedback_js_namespaceObject.handleFeedback)(report, feedbackOptions, data.logger)
196
+ });
197
+ }
180
198
  const registeredTool = toolName ? highLevelServer._registeredTools[toolName] : void 0;
181
199
  return await (0, external_instrumentation_js_namespaceObject.captureToolCall)({
182
200
  server,
@@ -1,10 +1,11 @@
1
+ import { SEND_FEEDBACK_TOOL_NAME, buildFeedbackEventProperties, buildFeedbackIntent, getFeedbackToolDescriptor, handleFeedback, parseFeedbackReport, resolveCollectFeedbackOptions } from "./feedback.mjs";
1
2
  import { analyticsOwnsParameter, getAnalyticsParameterOwnership, stripOwnedAnalyticsArguments } from "./analytics-parameters.mjs";
2
3
  import { isContextEnabled } from "./context-parameters.mjs";
3
4
  import { isCaptureModelEnabled } from "./model-parameters.mjs";
4
5
  import { MCPAnalyticsEventType } from "./event-types.mjs";
5
6
  import { getServerTrackingData } from "./internal.mjs";
6
7
  import { createWrappedTool, getToolFunction, hasToolFunction } from "./mcp-sdk-compat.mjs";
7
- import { handleReportMissing, resolveMissingCapabilityToolName } from "./tools.mjs";
8
+ import { getReportMissingToolDescriptor, handleReportMissing, resolveMissingCapabilityToolName } from "./tools.mjs";
8
9
  import { captureToolCall, getVirtualToolParameterOwnership, handleInitializeRequest, handleListToolsRequest, isToolAdvertised, patchRequestHandlers, readToolMetaCategory, traceResourceRequest } from "./instrumentation.mjs";
9
10
  import { getContextArgument } from "./tracing-helpers.mjs";
10
11
  const wrappedCallbacks = new WeakMap();
@@ -139,12 +140,29 @@ async function handleToolCallRequest(highLevelServer, server, originalCallToolHa
139
140
  extra,
140
141
  eventType: MCPAnalyticsEventType.mcpMissingCapability,
141
142
  explicitContextIntent: context,
142
- parameterOwnership: getVirtualToolParameterOwnership(data, toolName),
143
+ parameterOwnership: getVirtualToolParameterOwnership(data, toolName, getReportMissingToolDescriptor(toolName).inputSchema),
143
144
  execute: async ()=>handleReportMissing({
144
145
  context
145
146
  }, data.logger)
146
147
  });
147
148
  }
149
+ const feedbackOptions = resolveCollectFeedbackOptions(data.options.collectFeedback);
150
+ const isFeedbackCandidate = void 0 !== feedbackOptions && toolName === (feedbackOptions.toolName ?? SEND_FEEDBACK_TOOL_NAME);
151
+ if (isFeedbackCandidate && await isToolAdvertised(server, toolName, extra, data.logger) === false) {
152
+ const report = parseFeedbackReport(request.params?.arguments, feedbackOptions);
153
+ return await captureToolCall({
154
+ server,
155
+ data,
156
+ request,
157
+ extra,
158
+ eventType: MCPAnalyticsEventType.mcpFeedback,
159
+ explicitContextIntent: buildFeedbackIntent(report),
160
+ omitCapturedParameters: true,
161
+ extraEventProperties: buildFeedbackEventProperties(report),
162
+ parameterOwnership: getVirtualToolParameterOwnership(data, toolName, getFeedbackToolDescriptor(feedbackOptions).inputSchema),
163
+ execute: async ()=>handleFeedback(report, feedbackOptions, data.logger)
164
+ });
165
+ }
148
166
  const registeredTool = toolName ? highLevelServer._registeredTools[toolName] : void 0;
149
167
  return await captureToolCall({
150
168
  server,
@@ -1 +1 @@
1
- {"version":3,"file":"instrument-lowlevel.d.ts","sourceRoot":"","sources":["../../src/extensions/instrument-lowlevel.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAiD,aAAa,EAAE,MAAM,UAAU,CAAA;AAG5F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAqBxC;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CA8BtF"}
1
+ {"version":3,"file":"instrument-lowlevel.d.ts","sourceRoot":"","sources":["../../src/extensions/instrument-lowlevel.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAiD,aAAa,EAAE,MAAM,UAAU,CAAA;AAY5F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAqBxC;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CA8BtF"}
@@ -30,6 +30,7 @@ __webpack_require__.r(__webpack_exports__);
30
30
  __webpack_require__.d(__webpack_exports__, {
31
31
  instrumentLowLevelServer: ()=>instrumentLowLevelServer
32
32
  });
33
+ const external_feedback_js_namespaceObject = require("./feedback.js");
33
34
  const external_event_types_js_namespaceObject = require("./event-types.js");
34
35
  const external_internal_js_namespaceObject = require("./internal.js");
35
36
  const external_tools_js_namespaceObject = require("./tools.js");
@@ -74,12 +75,29 @@ async function handleToolCallRequest(server, originalCallToolHandler, request, e
74
75
  extra,
75
76
  eventType: external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpMissingCapability,
76
77
  explicitContextIntent: context,
77
- parameterOwnership: (0, external_instrumentation_js_namespaceObject.getVirtualToolParameterOwnership)(data, toolName),
78
+ parameterOwnership: (0, external_instrumentation_js_namespaceObject.getVirtualToolParameterOwnership)(data, toolName, (0, external_tools_js_namespaceObject.getReportMissingToolDescriptor)(toolName).inputSchema),
78
79
  execute: async ()=>(0, external_tools_js_namespaceObject.handleReportMissing)({
79
80
  context
80
81
  }, data.logger)
81
82
  });
82
83
  }
84
+ const feedbackOptions = (0, external_feedback_js_namespaceObject.resolveCollectFeedbackOptions)(data.options.collectFeedback);
85
+ const isFeedbackCandidate = void 0 !== feedbackOptions && toolName === (feedbackOptions.toolName ?? external_feedback_js_namespaceObject.SEND_FEEDBACK_TOOL_NAME);
86
+ if (isFeedbackCandidate && await (0, external_instrumentation_js_namespaceObject.isToolAdvertised)(server, toolName, extra, data.logger) === false) {
87
+ const report = (0, external_feedback_js_namespaceObject.parseFeedbackReport)(request.params?.arguments, feedbackOptions);
88
+ return await (0, external_instrumentation_js_namespaceObject.captureToolCall)({
89
+ server,
90
+ data,
91
+ request,
92
+ extra,
93
+ eventType: external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpFeedback,
94
+ explicitContextIntent: (0, external_feedback_js_namespaceObject.buildFeedbackIntent)(report),
95
+ omitCapturedParameters: true,
96
+ extraEventProperties: (0, external_feedback_js_namespaceObject.buildFeedbackEventProperties)(report),
97
+ parameterOwnership: (0, external_instrumentation_js_namespaceObject.getVirtualToolParameterOwnership)(data, toolName, (0, external_feedback_js_namespaceObject.getFeedbackToolDescriptor)(feedbackOptions).inputSchema),
98
+ execute: async ()=>(0, external_feedback_js_namespaceObject.handleFeedback)(report, feedbackOptions, data.logger)
99
+ });
100
+ }
83
101
  return await (0, external_instrumentation_js_namespaceObject.captureToolCall)({
84
102
  server,
85
103
  data,
@@ -1,6 +1,7 @@
1
+ import { SEND_FEEDBACK_TOOL_NAME, buildFeedbackEventProperties, buildFeedbackIntent, getFeedbackToolDescriptor, handleFeedback, parseFeedbackReport, resolveCollectFeedbackOptions } from "./feedback.mjs";
1
2
  import { MCPAnalyticsEventType } from "./event-types.mjs";
2
3
  import { getServerTrackingData } from "./internal.mjs";
3
- import { handleReportMissing, resolveMissingCapabilityToolName } from "./tools.mjs";
4
+ import { getReportMissingToolDescriptor, handleReportMissing, resolveMissingCapabilityToolName } from "./tools.mjs";
4
5
  import { captureToolCall, getVirtualToolParameterOwnership, handleInitializeRequest, handleListToolsRequest, isToolAdvertised, patchRequestHandlers, registerFallbackRequestHandler, traceResourceRequest } from "./instrumentation.mjs";
5
6
  import { getContextArgument } from "./tracing-helpers.mjs";
6
7
  function instrumentLowLevelServer(server, logger) {
@@ -42,12 +43,29 @@ async function handleToolCallRequest(server, originalCallToolHandler, request, e
42
43
  extra,
43
44
  eventType: MCPAnalyticsEventType.mcpMissingCapability,
44
45
  explicitContextIntent: context,
45
- parameterOwnership: getVirtualToolParameterOwnership(data, toolName),
46
+ parameterOwnership: getVirtualToolParameterOwnership(data, toolName, getReportMissingToolDescriptor(toolName).inputSchema),
46
47
  execute: async ()=>handleReportMissing({
47
48
  context
48
49
  }, data.logger)
49
50
  });
50
51
  }
52
+ const feedbackOptions = resolveCollectFeedbackOptions(data.options.collectFeedback);
53
+ const isFeedbackCandidate = void 0 !== feedbackOptions && toolName === (feedbackOptions.toolName ?? SEND_FEEDBACK_TOOL_NAME);
54
+ if (isFeedbackCandidate && await isToolAdvertised(server, toolName, extra, data.logger) === false) {
55
+ const report = parseFeedbackReport(request.params?.arguments, feedbackOptions);
56
+ return await captureToolCall({
57
+ server,
58
+ data,
59
+ request,
60
+ extra,
61
+ eventType: MCPAnalyticsEventType.mcpFeedback,
62
+ explicitContextIntent: buildFeedbackIntent(report),
63
+ omitCapturedParameters: true,
64
+ extraEventProperties: buildFeedbackEventProperties(report),
65
+ parameterOwnership: getVirtualToolParameterOwnership(data, toolName, getFeedbackToolDescriptor(feedbackOptions).inputSchema),
66
+ execute: async ()=>handleFeedback(report, feedbackOptions, data.logger)
67
+ });
68
+ }
51
69
  return await captureToolCall({
52
70
  server,
53
71
  data,
@@ -1,4 +1,4 @@
1
- import type { AnalyticsParameterOwnership, CompatibleRequestHandlerExtra, CompatibleToolsListLike, MCPAnalyticsData, MCPRequestLike, MCPServerLike } from '../types';
1
+ import type { AnalyticsParameterOwnership, CompatibleRequestHandlerExtra, CompatibleToolsListLike, JsonRecord, MCPAnalyticsData, MCPRequestLike, MCPServerLike } from '../types';
2
2
  import { MCPAnalyticsEventType } from './event-types';
3
3
  import type { LoggerFn } from './logger';
4
4
  /**
@@ -21,16 +21,29 @@ interface TraceToolCallParams {
21
21
  parameterOwnership?: AnalyticsParameterOwnership;
22
22
  /**
23
23
  * Event type to capture. Defaults to a tool call; the `get_more_tools` virtual
24
- * tool passes `mcpMissingCapability` so it records a capability gap rather than
25
- * a tool invocation.
24
+ * tool passes `mcpMissingCapability` and `send_feedback` passes
25
+ * `mcpFeedback`, so they record a capability gap / a feedback report
26
+ * rather than a tool invocation.
26
27
  */
27
28
  eventType?: MCPAnalyticsEventType;
28
29
  /**
29
30
  * When set, used verbatim as the captured intent (source `context_parameter`)
30
- * instead of running `resolveToolCallIntent`. Used by the `get_more_tools`
31
- * virtual tool, which carries its intent in the `context` argument.
31
+ * instead of running `resolveToolCallIntent`. Used by the virtual tools, which
32
+ * carry their intent in their own arguments.
32
33
  */
33
34
  explicitContextIntent?: string;
35
+ /**
36
+ * Extra event properties spread onto the captured event, after the host's
37
+ * `eventProperties`. Used by `send_feedback` for its `$mcp_feedback_*` fields.
38
+ */
39
+ extraEventProperties?: JsonRecord;
40
+ /**
41
+ * Drop the generically captured `$mcp_parameters` from the event. Used by
42
+ * `send_feedback`: its arguments are agent-narrated free text, so the
43
+ * PII-redacted `$mcp_feedback_*` properties are the captured surface — the
44
+ * raw arguments would bypass that redaction and record undeclared fields.
45
+ */
46
+ omitCapturedParameters?: boolean;
34
47
  /**
35
48
  * Optional accessor for an error the executor captured out-of-band. The
36
49
  * high-level SDK turns thrown tool errors into `isError: true` results before
@@ -88,21 +101,25 @@ export declare function registerFallbackRequestHandler(server: MCPServerLike, ha
88
101
  */
89
102
  export declare function patchRequestHandlers(server: MCPServerLike, patches: Record<string, HandlerPatch>): void;
90
103
  /**
91
- * Ownership for the `get_more_tools` virtual tool, resolved without the
92
- * `tools/list` cache.
104
+ * Ownership for an SDK-owned virtual tool (`get_more_tools`, `send_feedback`),
105
+ * resolved from its own descriptor rather than the `tools/list` cache.
93
106
  *
94
- * The missing-capability branch is only entered when the application does not
95
- * advertise a tool by this name, so the descriptor is the SDK's own and what it
96
- * declares is known statically. An instance that never served a listing — a
97
- * per-request `McpServer`/`Server`, the topology in ADR-0011 — would otherwise
98
- * read every injected parameter as not-ours and neither capture nor strip it.
107
+ * A virtual-tool branch is only entered when the application does not advertise
108
+ * a tool by this name, so the descriptor is the SDK's own and what it declares
109
+ * is known statically. An instance that never served a listing — a per-request
110
+ * `McpServer`/`Server`, the topology in ADR-0011 — would otherwise read every
111
+ * injected parameter as not-ours and neither capture nor strip it.
99
112
  *
100
113
  * `conversation_id` still comes from the cache on purpose: resolving it here too
101
114
  * would start minting a handle, and appending its prompt-back block, on
102
115
  * instances that today mint none. That changes session anchoring (ADR-0004)
103
116
  * rather than closing this gap.
117
+ *
118
+ * `virtualToolInputSchema` is required (not defaulted to one specific virtual
119
+ * tool's descriptor) so every call site names the tool it means; each caller
120
+ * passes its own descriptor's `inputSchema`.
104
121
  */
105
- export declare function getVirtualToolParameterOwnership(data: MCPAnalyticsData, toolName: string): AnalyticsParameterOwnership;
122
+ export declare function getVirtualToolParameterOwnership(data: MCPAnalyticsData, toolName: string, virtualToolInputSchema: unknown): AnalyticsParameterOwnership;
106
123
  /**
107
124
  * Checks the server's raw listing for a real owner of a candidate virtual tool.
108
125
  * This does not depend on a previous client request and does not call the
@@ -1 +1 @@
1
- {"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/extensions/instrumentation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/extensions/instrumentation.ts"],"names":[],"mappings":"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"}
@@ -57,18 +57,25 @@ const external_mcp_sdk_compat_js_namespaceObject = require("./mcp-sdk-compat.js"
57
57
  const external_request_headers_js_namespaceObject = require("./request-headers.js");
58
58
  const external_session_js_namespaceObject = require("./session.js");
59
59
  const external_session_token_js_namespaceObject = require("./session-token.js");
60
+ const external_feedback_js_namespaceObject = require("./feedback.js");
60
61
  const external_tools_js_namespaceObject = require("./tools.js");
61
62
  const external_tracing_helpers_js_namespaceObject = require("./tracing-helpers.js");
62
63
  async function captureToolCall(params) {
63
- const { server, data, request, extra, execute, parameterOwnership, eventType, explicitContextIntent, takeCapturedError } = params;
64
+ const { server, data, request, extra, execute, parameterOwnership, eventType, explicitContextIntent, extraEventProperties, omitCapturedParameters, takeCapturedError } = params;
64
65
  const resolvedEventType = eventType ?? external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpToolsCall;
65
- const ownership = getActiveAnalyticsParameterOwnership(data, request.params?.name, parameterOwnership, resolvedEventType === external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpMissingCapability);
66
- const canCaptureContextIntent = resolvedEventType !== external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpMissingCapability && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(data.options.context) && (ownership.context || !ownership.contextOwnershipKnown);
66
+ const isVirtualAnalyticsTool = resolvedEventType === external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpMissingCapability || resolvedEventType === external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpFeedback;
67
+ const ownership = getActiveAnalyticsParameterOwnership(data, request.params?.name, parameterOwnership, isVirtualAnalyticsTool);
68
+ const canCaptureContextIntent = !isVirtualAnalyticsTool && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(data.options.context) && (ownership.context || !ownership.contextOwnershipKnown);
67
69
  const conversation = (0, external_conversation_id_js_namespaceObject.resolveConversationId)(ownership.conversationId, request.params?.arguments);
68
70
  const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership);
69
71
  const startTime = new Date();
70
72
  const preparedEvent = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType, canCaptureContextIntent);
71
73
  if (preparedEvent && explicitContextIntent) (0, external_intent_js_namespaceObject.setExplicitContextIntent)(preparedEvent.event, explicitContextIntent);
74
+ if (preparedEvent && extraEventProperties) preparedEvent.event.properties = {
75
+ ...preparedEvent.event.properties,
76
+ ...extraEventProperties
77
+ };
78
+ if (preparedEvent && omitCapturedParameters) preparedEvent.event.parameters = void 0;
72
79
  let result;
73
80
  try {
74
81
  result = await execute(downstreamRequest);
@@ -80,12 +87,12 @@ async function captureToolCall(params) {
80
87
  publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError, result);
81
88
  return finalResult;
82
89
  }
83
- function getActiveAnalyticsParameterOwnership(data, toolName, override, isMissingCapabilityTool) {
90
+ function getActiveAnalyticsParameterOwnership(data, toolName, override, isVirtualAnalyticsTool) {
84
91
  const listed = toolName ? data.toolAnalyticsParameterOwnership.get(toolName) : void 0;
85
92
  const ownership = override ?? listed;
86
93
  return {
87
94
  contextOwnershipKnown: void 0 !== ownership,
88
- context: !isMissingCapabilityTool && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(data.options.context) && ownership?.context === true,
95
+ context: !isVirtualAnalyticsTool && (0, external_context_parameters_js_namespaceObject.isContextEnabled)(data.options.context) && ownership?.context === true,
89
96
  conversationId: true === data.options.enableConversationId && ownership?.conversationId === true,
90
97
  llmModel: (0, external_model_parameters_js_namespaceObject.isCaptureModelEnabled)(data.options.captureModel) && ownership?.llmModel === true,
91
98
  outputInstructions: true === data.options.enableConversationId && listed?.outputInstructions === true
@@ -289,9 +296,9 @@ function patchRequestHandlers(server, patches) {
289
296
  return result;
290
297
  };
291
298
  }
292
- function getVirtualToolParameterOwnership(data, toolName) {
299
+ function getVirtualToolParameterOwnership(data, toolName, virtualToolInputSchema) {
293
300
  return {
294
- ...(0, external_analytics_parameters_js_namespaceObject.getAnalyticsParameterOwnership)((0, external_tools_js_namespaceObject.getReportMissingToolDescriptor)(toolName).inputSchema),
301
+ ...(0, external_analytics_parameters_js_namespaceObject.getAnalyticsParameterOwnership)(virtualToolInputSchema),
295
302
  conversationId: data.toolAnalyticsParameterOwnership.get(toolName)?.conversationId === true
296
303
  };
297
304
  }
@@ -377,6 +384,19 @@ async function getTracedToolsList(server, originalListToolsHandler, request, ext
377
384
  ]);
378
385
  }
379
386
  }
387
+ const feedbackOptions = (0, external_feedback_js_namespaceObject.resolveCollectFeedbackOptions)(data.options.collectFeedback);
388
+ if (feedbackOptions) {
389
+ const feedbackToolName = feedbackOptions.toolName ?? external_feedback_js_namespaceObject.SEND_FEEDBACK_TOOL_NAME;
390
+ const alreadyPresent = tools.some((tool)=>tool?.name === feedbackToolName);
391
+ if (alreadyPresent) data.logger(`Warning: Cannot inject agent-feedback tool "${feedbackToolName}" because a real tool already uses that name. The real tool will not be intercepted.`);
392
+ else {
393
+ const virtualTool = (0, external_feedback_js_namespaceObject.getFeedbackToolDescriptor)(feedbackOptions);
394
+ tools.push(virtualTool);
395
+ cacheToolAnalyticsParameterOwnership(data.toolAnalyticsParameterOwnership, [
396
+ virtualTool
397
+ ]);
398
+ }
399
+ }
380
400
  if ((0, external_model_parameters_js_namespaceObject.isCaptureModelEnabled)(data.options.captureModel)) tools = (0, external_model_parameters_js_namespaceObject.addModelParameterToTools)(tools, (0, external_model_parameters_js_namespaceObject.getModelDescription)(data.options.captureModel), data.logger);
381
401
  if (data.options.enableConversationId) {
382
402
  tools = (0, external_conversation_id_js_namespaceObject.addConversationIdToTools)(tools, data.logger);
@@ -15,18 +15,25 @@ import { readRequestHandlerMethod } from "./mcp-sdk-compat.mjs";
15
15
  import { getRequestHeaders } from "./request-headers.mjs";
16
16
  import { getSessionId, getSessionInfo, isModernEraRequest, newSessionId } from "./session.mjs";
17
17
  import { encodeSessionId, readMcpSessionHeader, writeSessionIdToTransport } from "./session-token.mjs";
18
+ import { SEND_FEEDBACK_TOOL_NAME, getFeedbackToolDescriptor, resolveCollectFeedbackOptions } from "./feedback.mjs";
18
19
  import { getReportMissingToolDescriptor, resolveMissingCapabilityToolName } from "./tools.mjs";
19
20
  import { applyResolvedMetadata, isToolResultError } from "./tracing-helpers.mjs";
20
21
  async function captureToolCall(params) {
21
- const { server, data, request, extra, execute, parameterOwnership, eventType, explicitContextIntent, takeCapturedError } = params;
22
+ const { server, data, request, extra, execute, parameterOwnership, eventType, explicitContextIntent, extraEventProperties, omitCapturedParameters, takeCapturedError } = params;
22
23
  const resolvedEventType = eventType ?? MCPAnalyticsEventType.mcpToolsCall;
23
- const ownership = getActiveAnalyticsParameterOwnership(data, request.params?.name, parameterOwnership, resolvedEventType === MCPAnalyticsEventType.mcpMissingCapability);
24
- const canCaptureContextIntent = resolvedEventType !== MCPAnalyticsEventType.mcpMissingCapability && isContextEnabled(data.options.context) && (ownership.context || !ownership.contextOwnershipKnown);
24
+ const isVirtualAnalyticsTool = resolvedEventType === MCPAnalyticsEventType.mcpMissingCapability || resolvedEventType === MCPAnalyticsEventType.mcpFeedback;
25
+ const ownership = getActiveAnalyticsParameterOwnership(data, request.params?.name, parameterOwnership, isVirtualAnalyticsTool);
26
+ const canCaptureContextIntent = !isVirtualAnalyticsTool && isContextEnabled(data.options.context) && (ownership.context || !ownership.contextOwnershipKnown);
25
27
  const conversation = resolveConversationId(ownership.conversationId, request.params?.arguments);
26
28
  const downstreamRequest = cloneRequestWithoutOwnedAnalyticsArguments(request, ownership);
27
29
  const startTime = new Date();
28
30
  const preparedEvent = await prepareToolCallEvent(server, data, request, downstreamRequest, extra, startTime, conversation, ownership, resolvedEventType, canCaptureContextIntent);
29
31
  if (preparedEvent && explicitContextIntent) setExplicitContextIntent(preparedEvent.event, explicitContextIntent);
32
+ if (preparedEvent && extraEventProperties) preparedEvent.event.properties = {
33
+ ...preparedEvent.event.properties,
34
+ ...extraEventProperties
35
+ };
36
+ if (preparedEvent && omitCapturedParameters) preparedEvent.event.parameters = void 0;
30
37
  let result;
31
38
  try {
32
39
  result = await execute(downstreamRequest);
@@ -38,12 +45,12 @@ async function captureToolCall(params) {
38
45
  publishSuccessfulToolEvent(server, preparedEvent, finalResult, startTime, data.logger, takeCapturedError, result);
39
46
  return finalResult;
40
47
  }
41
- function getActiveAnalyticsParameterOwnership(data, toolName, override, isMissingCapabilityTool) {
48
+ function getActiveAnalyticsParameterOwnership(data, toolName, override, isVirtualAnalyticsTool) {
42
49
  const listed = toolName ? data.toolAnalyticsParameterOwnership.get(toolName) : void 0;
43
50
  const ownership = override ?? listed;
44
51
  return {
45
52
  contextOwnershipKnown: void 0 !== ownership,
46
- context: !isMissingCapabilityTool && isContextEnabled(data.options.context) && ownership?.context === true,
53
+ context: !isVirtualAnalyticsTool && isContextEnabled(data.options.context) && ownership?.context === true,
47
54
  conversationId: true === data.options.enableConversationId && ownership?.conversationId === true,
48
55
  llmModel: isCaptureModelEnabled(data.options.captureModel) && ownership?.llmModel === true,
49
56
  outputInstructions: true === data.options.enableConversationId && listed?.outputInstructions === true
@@ -247,9 +254,9 @@ function patchRequestHandlers(server, patches) {
247
254
  return result;
248
255
  };
249
256
  }
250
- function getVirtualToolParameterOwnership(data, toolName) {
257
+ function getVirtualToolParameterOwnership(data, toolName, virtualToolInputSchema) {
251
258
  return {
252
- ...getAnalyticsParameterOwnership(getReportMissingToolDescriptor(toolName).inputSchema),
259
+ ...getAnalyticsParameterOwnership(virtualToolInputSchema),
253
260
  conversationId: data.toolAnalyticsParameterOwnership.get(toolName)?.conversationId === true
254
261
  };
255
262
  }
@@ -335,6 +342,19 @@ async function getTracedToolsList(server, originalListToolsHandler, request, ext
335
342
  ]);
336
343
  }
337
344
  }
345
+ const feedbackOptions = resolveCollectFeedbackOptions(data.options.collectFeedback);
346
+ if (feedbackOptions) {
347
+ const feedbackToolName = feedbackOptions.toolName ?? SEND_FEEDBACK_TOOL_NAME;
348
+ const alreadyPresent = tools.some((tool)=>tool?.name === feedbackToolName);
349
+ if (alreadyPresent) data.logger(`Warning: Cannot inject agent-feedback tool "${feedbackToolName}" because a real tool already uses that name. The real tool will not be intercepted.`);
350
+ else {
351
+ const virtualTool = getFeedbackToolDescriptor(feedbackOptions);
352
+ tools.push(virtualTool);
353
+ cacheToolAnalyticsParameterOwnership(data.toolAnalyticsParameterOwnership, [
354
+ virtualTool
355
+ ]);
356
+ }
357
+ }
338
358
  if (isCaptureModelEnabled(data.options.captureModel)) tools = addModelParameterToTools(tools, getModelDescription(data.options.captureModel), data.logger);
339
359
  if (data.options.enableConversationId) {
340
360
  tools = addConversationIdToTools(tools, data.logger);